costway-tracking-v2 2.0.0
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/.cursor/README.md +150 -0
- package/.cursor/cursors-intellisense-rules.json +62 -0
- package/.cursor/rules.json +50 -0
- package/.cursor/settings.json +32 -0
- package/.cursor/snippets.json +186 -0
- package/README.md +223 -0
- package/index.cjs +494 -0
- package/index.html +42 -0
- package/package.json +20 -0
- package/tracking-dev.js +463 -0
- package/tracking.js +464 -0
- package/trackvue3.js +526 -0
package/trackvue3.js
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
let config = {
|
|
2
|
+
batchSize: 50,
|
|
3
|
+
debounceTime: 200,
|
|
4
|
+
targetContainerSelector: 'body',
|
|
5
|
+
targetClass: '.costway-track-class',
|
|
6
|
+
trackClickSelector: '[data-track]',
|
|
7
|
+
apiUrl: '/goapi/v1/stat/track',
|
|
8
|
+
country: "us",
|
|
9
|
+
sessionTimeout: 30 * 60 * 1000
|
|
10
|
+
};
|
|
11
|
+
let eventData = [];
|
|
12
|
+
let pageEnterTime;
|
|
13
|
+
|
|
14
|
+
let heartbeat;
|
|
15
|
+
let lastInterval;
|
|
16
|
+
let hasTrackedPageLeave = false;
|
|
17
|
+
config.sessionTimeout = 30 * 60 * 1000; // 30分钟
|
|
18
|
+
let entryReferrer = isBrowser() ? (document.referrer || '') : ''; // 用户首次进入站点时的referrer
|
|
19
|
+
|
|
20
|
+
let currentRoute = isBrowser() ? window.location.pathname : "";
|
|
21
|
+
let simulatedReferrer = currentRoute; // 初始化为entryReferrer
|
|
22
|
+
|
|
23
|
+
let lastRoute = "";
|
|
24
|
+
function isBrowser() {
|
|
25
|
+
return typeof window !== 'undefined';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSession() {
|
|
29
|
+
if (!isBrowser()) {
|
|
30
|
+
return; // 如果不在浏览器环境中,则直接返回
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let now = new Date().getTime();
|
|
34
|
+
let currentReferrer = simulatedReferrer;
|
|
35
|
+
let currentUTM_S = getUrlParameter('utm_source');
|
|
36
|
+
let currentUTM_m = getUrlParameter('utm_medium');
|
|
37
|
+
let currentUTM = currentUTM_S + currentUTM_m;
|
|
38
|
+
const currentDomain = normalizeDomain(window.location.hostname);
|
|
39
|
+
const referrerTag = getReferrerTag(currentReferrer, currentDomain);
|
|
40
|
+
let previousSession = isBrowser() ? JSON.parse(localStorage.getItem("cgs_session")) : null;
|
|
41
|
+
let sessionMap = isBrowser() ? (JSON.parse(localStorage.getItem("cgs_sessionMap")) || {}) : {};
|
|
42
|
+
|
|
43
|
+
// 检查会话是否超时
|
|
44
|
+
let isSessionTimedOut = previousSession && (now - previousSession.startTime > config.sessionTimeout);
|
|
45
|
+
if (!previousSession || (isSessionTimedOut) || (referrerTag !== "INTERNAL" && (referrerTag !== previousSession.referrerTag || currentUTM !== previousSession.initialUtmForSession))) {
|
|
46
|
+
let newSessionId = sessionMap[`${referrerTag}_${currentUTM}`];
|
|
47
|
+
if (!newSessionId) {
|
|
48
|
+
newSessionId = generateUUID();
|
|
49
|
+
sessionMap[`${referrerTag}_${currentUTM}`] = newSessionId;
|
|
50
|
+
localStorage.setItem("cgs_sessionMap", JSON.stringify(sessionMap));
|
|
51
|
+
}
|
|
52
|
+
if (isSessionTimedOut) {
|
|
53
|
+
newSessionId = generateUUID();
|
|
54
|
+
sessionMap[`${referrerTag}_${currentUTM}`] = newSessionId;
|
|
55
|
+
localStorage.setItem("cgs_sessionMap", JSON.stringify(sessionMap));
|
|
56
|
+
}
|
|
57
|
+
let newSession = {
|
|
58
|
+
id: newSessionId,
|
|
59
|
+
startTime: now,
|
|
60
|
+
lastActivityTime: now,
|
|
61
|
+
referrerTag: referrerTag,
|
|
62
|
+
referrerForSession: currentReferrer,
|
|
63
|
+
initialUtmForSession: currentUTM,
|
|
64
|
+
};
|
|
65
|
+
localStorage.setItem("cgs_session", JSON.stringify(newSession));
|
|
66
|
+
localStorage.setItem("cgs_sessionID", newSession.id);
|
|
67
|
+
setCookie("cgs_sessionID", newSession.id, 1); // Save for 1 day
|
|
68
|
+
return newSession;
|
|
69
|
+
} else {
|
|
70
|
+
previousSession.lastActivityTime = now;
|
|
71
|
+
localStorage.setItem("cgs_session", JSON.stringify(previousSession));
|
|
72
|
+
return previousSession;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getReferrerTag() {
|
|
76
|
+
const currentDomain = normalizeDomain(window.location.hostname);
|
|
77
|
+
const referrerDomain = extractDomain(simulatedReferrer); // 使用模拟的referrer
|
|
78
|
+
if (referrerDomain === currentDomain || !simulatedReferrer) {
|
|
79
|
+
return 'INTERNAL';
|
|
80
|
+
} else {
|
|
81
|
+
return referrerDomain;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
function getUrlParameter(name) {
|
|
87
|
+
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
|
88
|
+
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
|
89
|
+
var results = regex.exec(window.location.search);
|
|
90
|
+
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
|
91
|
+
};
|
|
92
|
+
function setCookie(name, value, days) {
|
|
93
|
+
if (!isBrowser()) return;
|
|
94
|
+
let expires = "";
|
|
95
|
+
if (days) {
|
|
96
|
+
let date = new Date();
|
|
97
|
+
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
98
|
+
expires = "; expires=" + date.toUTCString();
|
|
99
|
+
}
|
|
100
|
+
document.cookie = name + "=" + (value || "") + expires + "; Domain=.costway.com; path=/";
|
|
101
|
+
}
|
|
102
|
+
function getCookie(name) {
|
|
103
|
+
if (!isBrowser()) return;
|
|
104
|
+
let nameEQ = name + "=";
|
|
105
|
+
let ca = document.cookie.split(';');
|
|
106
|
+
for (let i = 0; i < ca.length; i++) {
|
|
107
|
+
let c = ca[i];
|
|
108
|
+
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
|
109
|
+
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function setuserId(userId) {
|
|
114
|
+
if (!isBrowser()) return;
|
|
115
|
+
localStorage.setItem('costway_t_userId', userId);
|
|
116
|
+
setCookie('costway_t_userId', userId, 365); // 保存365天
|
|
117
|
+
}
|
|
118
|
+
function normalizeDomain(domain) {
|
|
119
|
+
if (domain.startsWith("www.")) {
|
|
120
|
+
return domain.slice(4);
|
|
121
|
+
}
|
|
122
|
+
return domain;
|
|
123
|
+
}
|
|
124
|
+
function extractDomain(url) {
|
|
125
|
+
if (!url) {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
// 从URL中提取域名
|
|
129
|
+
const domainRegex = /^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n]+)/im;
|
|
130
|
+
const match = url.match(domainRegex);
|
|
131
|
+
if (match && match.length > 1) {
|
|
132
|
+
return match[1];
|
|
133
|
+
} else {
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
function clearTimers() {
|
|
140
|
+
clearTimeout(heartbeat);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getTime() {
|
|
144
|
+
return (new Date()).getTime();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function intervalHeartbeat() {
|
|
148
|
+
var now = getTime();
|
|
149
|
+
var diff = now - lastInterval - 200;
|
|
150
|
+
lastInterval = now;
|
|
151
|
+
if (diff > 1000) { // don't trigger on small stutters less than 1000ms
|
|
152
|
+
clearTimers();
|
|
153
|
+
trackPageLeave(); // call trackPageLeave when heartbeat stops
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
function debounce(func, wait) {
|
|
159
|
+
let timeout;
|
|
160
|
+
return function () {
|
|
161
|
+
const context = this;
|
|
162
|
+
const args = arguments;
|
|
163
|
+
clearTimeout(timeout);
|
|
164
|
+
timeout = setTimeout(function () {
|
|
165
|
+
func.apply(context, args);
|
|
166
|
+
}, wait);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getuserId() {
|
|
171
|
+
var userId = localStorage.getItem('costway_t_userId');
|
|
172
|
+
|
|
173
|
+
if (!userId) {
|
|
174
|
+
userId = getCookie('costway_t_userId');
|
|
175
|
+
if (!userId) {
|
|
176
|
+
userId = generateUUID();
|
|
177
|
+
localStorage.setItem('costway_t_userId', userId);
|
|
178
|
+
setCookie('costway_t_userId', userId, 365); // 将用户ID保存到cookie中,有效期为1年
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return userId;
|
|
182
|
+
}
|
|
183
|
+
// 重写 UUID 生成
|
|
184
|
+
function generateUUID() {
|
|
185
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
186
|
+
var r = Math.random() * 16 | 0,
|
|
187
|
+
v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
188
|
+
return v.toString(16);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function sendDataImmediately(data) {
|
|
192
|
+
if (data.length === 0) return;
|
|
193
|
+
navigator.sendBeacon(config.apiUrl, JSON.stringify(data));
|
|
194
|
+
}
|
|
195
|
+
function sendEventData() {
|
|
196
|
+
if (eventData.length === 0) return;
|
|
197
|
+
navigator.sendBeacon(config.apiUrl, JSON.stringify(eventData));
|
|
198
|
+
eventData = [];
|
|
199
|
+
}
|
|
200
|
+
function getGlobalParams() {
|
|
201
|
+
if (window.customGlobalParams) {
|
|
202
|
+
return window.customGlobalParams;
|
|
203
|
+
} else {
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function trackEvent(eventType, data) {
|
|
208
|
+
let session = getSession();
|
|
209
|
+
const event = {
|
|
210
|
+
userId: getuserId(),
|
|
211
|
+
timestamp: new Date().getTime(),
|
|
212
|
+
eventType: eventType,
|
|
213
|
+
data: data,
|
|
214
|
+
params: getGlobalParams(),
|
|
215
|
+
country: config.country,
|
|
216
|
+
sessionId: session.id,
|
|
217
|
+
};
|
|
218
|
+
if (eventType === "pageView" || eventType === "pageLeave" || eventType === "error" || eventType === "customEvent" || eventType === "pageLoadTime") {
|
|
219
|
+
sendDataImmediately([event]);
|
|
220
|
+
} else {
|
|
221
|
+
eventData.push(event);
|
|
222
|
+
if (eventData.length >= config.batchSize) {
|
|
223
|
+
sendEventData();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
function trackPageView(initURL="") {
|
|
229
|
+
let session = getSession();
|
|
230
|
+
if (!simulatedReferrer.startsWith('http://') && !simulatedReferrer.startsWith('https://')) {
|
|
231
|
+
simulatedReferrer = `${window.location.protocol}//${window.location.hostname}${simulatedReferrer}`;
|
|
232
|
+
}
|
|
233
|
+
let url = initURL?initURL:currentRoute
|
|
234
|
+
const pageViewData = {
|
|
235
|
+
url: url,
|
|
236
|
+
referrer: simulatedReferrer,
|
|
237
|
+
sessionId: session.id,
|
|
238
|
+
};
|
|
239
|
+
trackEvent("pageView", pageViewData);
|
|
240
|
+
pageEnterTime = new Date().getTime();
|
|
241
|
+
}
|
|
242
|
+
function trackPageLeave() {
|
|
243
|
+
// if (hasTrackedPageLeave) {
|
|
244
|
+
// return; // 如果已经发送过数据,则直接返回
|
|
245
|
+
// }
|
|
246
|
+
if (!simulatedReferrer.startsWith('http://') && !simulatedReferrer.startsWith('https://')) {
|
|
247
|
+
simulatedReferrer = `${window.location.protocol}//${window.location.hostname}${simulatedReferrer}`;
|
|
248
|
+
}
|
|
249
|
+
let session = getSession();
|
|
250
|
+
const pageLeaveTime = new Date().getTime();
|
|
251
|
+
const duration = pageLeaveTime - pageEnterTime;
|
|
252
|
+
const totalStayTime = window.performance.timing.loadEventEnd - window.performance.timing.navigationStart;
|
|
253
|
+
const scrollDistance = window.scrollY;
|
|
254
|
+
const pageLeaveData = {
|
|
255
|
+
url: simulatedReferrer,
|
|
256
|
+
duration: duration,
|
|
257
|
+
totalStayTime: totalStayTime,
|
|
258
|
+
scrollDistance: scrollDistance,
|
|
259
|
+
sessionId: session.id,
|
|
260
|
+
};
|
|
261
|
+
trackEvent("pageLeave", pageLeaveData);
|
|
262
|
+
hasTrackedPageLeave = true;
|
|
263
|
+
}
|
|
264
|
+
function trackClick(event) {
|
|
265
|
+
let target = event.target;
|
|
266
|
+
if (target !== document.body && target !== document.documentElement) {
|
|
267
|
+
target = target.parentNode;
|
|
268
|
+
}
|
|
269
|
+
//if (target.tagName == "HTML" || target.tagName == "BODY") return
|
|
270
|
+
if (target.matches(config.trackClickSelector)) {
|
|
271
|
+
const clickData = {
|
|
272
|
+
element: target.tagName,
|
|
273
|
+
id: target.id,
|
|
274
|
+
classList: Array.from(target.classList),
|
|
275
|
+
dataset: target.dataset,
|
|
276
|
+
};
|
|
277
|
+
trackEvent("click", clickData);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function trackNewElement(element) {
|
|
281
|
+
// if (element.matches(config.targetClass)) {
|
|
282
|
+
const newElementData = {
|
|
283
|
+
element: element.tagName,
|
|
284
|
+
id: element.id,
|
|
285
|
+
classList: Array.from(element.classList),
|
|
286
|
+
};
|
|
287
|
+
trackEvent("newElement", newElementData);
|
|
288
|
+
// }
|
|
289
|
+
}
|
|
290
|
+
config.hideShowSelector = '*'; // All elements
|
|
291
|
+
function isVisible(element) {
|
|
292
|
+
const style = window.getComputedStyle(element);
|
|
293
|
+
const isHidden = style.display === 'none' || style.visibility === 'hidden';
|
|
294
|
+
return !isHidden;
|
|
295
|
+
}
|
|
296
|
+
function observeClassChanges() {
|
|
297
|
+
const observerCallback = debounce(function (mutations) {
|
|
298
|
+
mutations.forEach(function (mutation) {
|
|
299
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
|
300
|
+
const target = mutation.target;
|
|
301
|
+
if (mutation.oldValue && mutation.oldValue.includes("showActive") && !target.classList.contains("showActive")) {
|
|
302
|
+
const removedElementData = {
|
|
303
|
+
element: target.tagName,
|
|
304
|
+
id: target.id,
|
|
305
|
+
classList: Array.from(target.classList),
|
|
306
|
+
};
|
|
307
|
+
trackEvent("closeElement", removedElementData);
|
|
308
|
+
} else if (!mutation.oldValue || (!mutation.oldValue.includes("showActive") && target.classList.contains("showActive"))) {
|
|
309
|
+
const addedElementData = {
|
|
310
|
+
element: target.tagName,
|
|
311
|
+
id: target.id,
|
|
312
|
+
classList: Array.from(target.classList),
|
|
313
|
+
};
|
|
314
|
+
trackEvent("showElement", addedElementData);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}, config.debounceTime);
|
|
319
|
+
|
|
320
|
+
const observer = new MutationObserver(observerCallback);
|
|
321
|
+
const observerConfig = {
|
|
322
|
+
attributes: true,
|
|
323
|
+
attributeOldValue: true,
|
|
324
|
+
subtree: true,
|
|
325
|
+
};
|
|
326
|
+
const targetContainer = document.querySelector(config.targetContainerSelector);
|
|
327
|
+
observer.observe(targetContainer, observerConfig);
|
|
328
|
+
}
|
|
329
|
+
function observeDomChanges() {
|
|
330
|
+
// 使用“debounce”函数可以防止短时间内频繁触发事件
|
|
331
|
+
const observerCallback = debounce(function (mutations) {
|
|
332
|
+
mutations.forEach(function (mutation) {
|
|
333
|
+
mutation.addedNodes.forEach(function (node) {
|
|
334
|
+
// 判断新增的节点是否匹配目标类
|
|
335
|
+
if (
|
|
336
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
337
|
+
node.matches(config.targetClass)
|
|
338
|
+
) {
|
|
339
|
+
trackNewElement(node);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}, config.debounceTime);
|
|
344
|
+
|
|
345
|
+
const observer = new MutationObserver(observerCallback);
|
|
346
|
+
const observerConfig = {
|
|
347
|
+
childList: true,
|
|
348
|
+
subtree: true,
|
|
349
|
+
};
|
|
350
|
+
// 使用querySelector获取目标元素,而不是直接使用document.body
|
|
351
|
+
const targetContainer = document.querySelector(config.targetContainerSelector);
|
|
352
|
+
observer.observe(targetContainer, observerConfig);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function registerErrorListener() {
|
|
356
|
+
window.addEventListener('error', function (event) {
|
|
357
|
+
trackError(event.message, event.filename, event.lineno, event.colno, event.error);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
function trackError(message, source, lineno, colno, error) {
|
|
363
|
+
const errorData = {
|
|
364
|
+
message: message,
|
|
365
|
+
source: source,
|
|
366
|
+
lineno: lineno,
|
|
367
|
+
colno: colno,
|
|
368
|
+
error: error && error.stack,
|
|
369
|
+
};
|
|
370
|
+
trackEvent("error", errorData);
|
|
371
|
+
}
|
|
372
|
+
function trackPageLoadTime() {
|
|
373
|
+
if (window.performance) {
|
|
374
|
+
var performanceEntries = window.performance.getEntriesByType('navigation');
|
|
375
|
+
if (performanceEntries.length > 0) {
|
|
376
|
+
var navigationTiming = performanceEntries[0];
|
|
377
|
+
var pageLoadTime = navigationTiming.loadEventEnd - navigationTiming.startTime;
|
|
378
|
+
// ...
|
|
379
|
+
const data = {
|
|
380
|
+
pageLoadTime: pageLoadTime,
|
|
381
|
+
url: window.location.href,
|
|
382
|
+
};
|
|
383
|
+
trackEvent("pageLoadTime", data);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function trackResourceLoadTimes() {
|
|
389
|
+
if (window.performance) {
|
|
390
|
+
var resources = window.performance.getEntriesByType("resource");
|
|
391
|
+
resources.forEach(function (resource) {
|
|
392
|
+
var resourceName = resource.name;
|
|
393
|
+
var resourceLoadTime = resource.duration;
|
|
394
|
+
trackEvent("resourceLoadTime", { resourceName, resourceLoadTime });
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
// function setupRouterListener(router) {
|
|
401
|
+
// router.beforeEach((to, from, next) => {
|
|
402
|
+
// hasTrackedPageLeave = false;
|
|
403
|
+
// console.log('从', from.fullPath, '跳转到', to.fullPath);
|
|
404
|
+
// lastRoute = currentRoute;
|
|
405
|
+
// currentRoute = to.fullPath;
|
|
406
|
+
// simulatedReferrer = from.fullPath; // 更新模拟的referrer为当前页面的URL
|
|
407
|
+
// next();
|
|
408
|
+
// });
|
|
409
|
+
// router.afterEach((to, from) => {
|
|
410
|
+
// currentRoute = to.fullPath;
|
|
411
|
+
// simulatedReferrer = from.fullPath; // 在跟踪完页面视图后再次更新模拟的referrer
|
|
412
|
+
// trackPageLeave();
|
|
413
|
+
// trackPageView();
|
|
414
|
+
|
|
415
|
+
// });
|
|
416
|
+
// }
|
|
417
|
+
|
|
418
|
+
function setupRouterListener(router) {
|
|
419
|
+
router.beforeEach((to, from, next) => {
|
|
420
|
+
hasTrackedPageLeave = false;
|
|
421
|
+
const fromFullURL = `${window.location.protocol}//${window.location.hostname}${from.fullPath}`;
|
|
422
|
+
const toFullURL = `${window.location.protocol}//${window.location.hostname}${to.fullPath}`;
|
|
423
|
+
console.log('从', fromFullURL, '跳转到', toFullURL);
|
|
424
|
+
lastRoute = currentRoute;
|
|
425
|
+
currentRoute = toFullURL;
|
|
426
|
+
simulatedReferrer = fromFullURL; // 更新模拟的referrer为当前页面的完整URL
|
|
427
|
+
next();
|
|
428
|
+
});
|
|
429
|
+
router.afterEach((to, from) => {
|
|
430
|
+
const fromFullURL = `${window.location.protocol}//${window.location.hostname}${from.fullPath}`;
|
|
431
|
+
const toFullURL = `${window.location.protocol}//${window.location.hostname}${to.fullPath}`;
|
|
432
|
+
currentRoute = toFullURL;
|
|
433
|
+
simulatedReferrer = fromFullURL; // 在跟踪完页面视图后再次更新模拟的referrer
|
|
434
|
+
trackPageLeave();
|
|
435
|
+
trackPageView();
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
let lastVisibilityState = isBrowser() ? document.visibilityState : null;
|
|
441
|
+
let pageHiddenTime = null;
|
|
442
|
+
let pageVisibleTime = null;
|
|
443
|
+
let initURL = isBrowser() ? window.location.href : '';
|
|
444
|
+
function initTracking(router) {
|
|
445
|
+
if (!isBrowser()) {
|
|
446
|
+
return; // 如果不在浏览器环境中,则直接返回
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let userId = getuserId();
|
|
450
|
+
if (!userId) {
|
|
451
|
+
userId = generateUUID();
|
|
452
|
+
setuserId(userId);
|
|
453
|
+
}
|
|
454
|
+
simulatedReferrer = isBrowser() ? (document.referrer || window.location.href) : '';
|
|
455
|
+
|
|
456
|
+
trackPageView(initURL);
|
|
457
|
+
// trackPageLoadTime();
|
|
458
|
+
//trackResourceLoadTimes();
|
|
459
|
+
if (isBrowser()) {
|
|
460
|
+
document.addEventListener('click', trackClick);
|
|
461
|
+
|
|
462
|
+
document.addEventListener('visibilitychange', function () {
|
|
463
|
+
if (document.visibilityState === 'hidden') {
|
|
464
|
+
// 页面从可见变为不可见,记录时间
|
|
465
|
+
pageHiddenTime = new Date().getTime();
|
|
466
|
+
const visibleDuration = new Date().getTime() - pageVisibleTime;
|
|
467
|
+
trackPageLeave();
|
|
468
|
+
|
|
469
|
+
} else if (document.visibilityState === 'visible' && pageHiddenTime) {
|
|
470
|
+
// 页面从不可见变为可见,计算隐藏的时长
|
|
471
|
+
const hiddenDuration = new Date().getTime() - pageHiddenTime;
|
|
472
|
+
pageHiddenTime = null;
|
|
473
|
+
pageVisibleTime = new Date().getTime();
|
|
474
|
+
// 如果页面隐藏的时长超过一定阈值(例如30秒),则认为用户已离开页面
|
|
475
|
+
if (hiddenDuration > 30000) {
|
|
476
|
+
trackPageLeave();
|
|
477
|
+
}
|
|
478
|
+
// 触发 pageView 事件
|
|
479
|
+
trackPageView();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
//window.addEventListener('beforeunload', trackPageLeave);
|
|
484
|
+
window.addEventListener('load', function () { setTimeout(trackPageLoadTime, 0); });
|
|
485
|
+
window.addEventListener('pagehide', trackPageLeave);
|
|
486
|
+
window.addEventListener('DOMContentLoaded', function () {
|
|
487
|
+
observeDomChanges();
|
|
488
|
+
observeClassChanges();
|
|
489
|
+
});
|
|
490
|
+
registerErrorListener();
|
|
491
|
+
console.log('Configured country:', config.country);
|
|
492
|
+
if (router) { // 如果提供了路由对象,则设置路由监听器
|
|
493
|
+
setupRouterListener(router);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function trackCustomEvent(eventName, eventData) {
|
|
498
|
+
const data = {
|
|
499
|
+
userId: getuserId(),
|
|
500
|
+
timestamp: new Date().getTime(),
|
|
501
|
+
customData: eventData,
|
|
502
|
+
eventName: eventName,
|
|
503
|
+
};
|
|
504
|
+
trackEvent("customEvent", data);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
*
|
|
508
|
+
* setConfig({
|
|
509
|
+
country: 'USA',
|
|
510
|
+
// ... 其他配置项 ...
|
|
511
|
+
});}
|
|
512
|
+
*/
|
|
513
|
+
function setConfig(globalConfig) {
|
|
514
|
+
config = { ...config, ...globalConfig };
|
|
515
|
+
}
|
|
516
|
+
export {
|
|
517
|
+
initTracking,
|
|
518
|
+
trackCustomEvent,
|
|
519
|
+
setConfig,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// init();
|
|
523
|
+
// window.trackCustomEvent = trackCustomEvent;
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|