behavior-aliu 1.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/README.md +104 -0
- package/dist/index.cjs.js +252 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +250 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/sender.d.ts +6 -0
- package/dist/storage.d.ts +4 -0
- package/dist/tracker.d.ts +6 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# User Behavior Monitor SDK
|
|
2
|
+
|
|
3
|
+
轻量级、零依赖的前端用户行为监控 SDK。
|
|
4
|
+
专为现代 Web 应用设计,支持 PV/UV 统计、精准停留时长计算、用户点击行为追踪以及 SPA(单页应用)路由监控。
|
|
5
|
+
|
|
6
|
+
## 核心特性
|
|
7
|
+
|
|
8
|
+
- ** 基础指标**: 自动采集 **PV** (Page View) 和 **UV** (Unique Visitor)。
|
|
9
|
+
- ** 精准停留**: 基于 `visibilitychange` 和 `beforeunload` 精准计算页面实际停留时长。
|
|
10
|
+
- ** 点击追踪**: 通过 `data-track-click` 属性实现无侵入式全自动点击埋点。
|
|
11
|
+
- ** SPA 支持**: 完美支持 Vue/React 等单页应用的路由跳转监控(Hash & History 模式)。
|
|
12
|
+
- ** 稳健上报**: 优先使用 `navigator.sendBeacon` 确保页面关闭时数据不丢失,自动降级 `fetch`。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install
|
|
18
|
+
npm run build
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
### 1. 引入 SDK
|
|
24
|
+
|
|
25
|
+
支持 ES Module (推荐) 或 Script 标签引入。
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import { initUserBehaviorMonitor } from './dist/index.esm.js';
|
|
29
|
+
|
|
30
|
+
initUserBehaviorMonitor({
|
|
31
|
+
projectName: 'my-awesome-project', // 项目标识
|
|
32
|
+
reportUrl: 'http://your-server.com/report', // 数据上报地址
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. 自动捕获点击
|
|
37
|
+
|
|
38
|
+
只需在 HTML 元素上添加 `data-track-click` 属性,SDK 会自动捕获点击并上报。
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<!-- 点击后自动上报: { behavior: 'click', action: 'buy_btn', ... } -->
|
|
42
|
+
<button data-track-click="buy_btn">立即购买</button>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 运行 Demo
|
|
46
|
+
|
|
47
|
+
本项目内置了一个基于 Express 的测试环境,方便您直观体验监控效果。
|
|
48
|
+
|
|
49
|
+
1. **安装依赖**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
2. **构建 SDK**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run build
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. **启动 Demo**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run demo
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> 终端会显示:`测试服务器已启动! 👉 页面地址: http://localhost:3000/index.html`
|
|
68
|
+
|
|
69
|
+
4. **体验功能**
|
|
70
|
+
- 打开浏览器访问 `http://localhost:3000/index.html`
|
|
71
|
+
- **点击按钮**: 观察终端输出 `behavior: 'click'`
|
|
72
|
+
- **模拟路由跳转**: 点击 "模拟路由跳转",观察 `dwell` (旧页面停留) 和 `pv` (新页面) 上报
|
|
73
|
+
- **刷新/关闭页面**: 观察 `dwell` (页面卸载) 上报
|
|
74
|
+
|
|
75
|
+
## 数据格式示例
|
|
76
|
+
|
|
77
|
+
SDK 上报的数据格式如下 (JSON):
|
|
78
|
+
|
|
79
|
+
**PV (页面访问)**
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"behavior": "pv",
|
|
84
|
+
"projectName": "demo",
|
|
85
|
+
"pageUrl": "http://localhost:3000/",
|
|
86
|
+
"pv": 15,
|
|
87
|
+
"userId": "xxxx-xxxx"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Dwell (停留时长)**
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"behavior": "dwell",
|
|
96
|
+
"dwellTime": 4520, // 毫秒
|
|
97
|
+
"pageUrl": "http://localhost:3000/"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 开发
|
|
102
|
+
|
|
103
|
+
- `npm run dev`: 监听源码变化并自动重新构建
|
|
104
|
+
- `npm run build`: 生产环境构建 (输出到 `dist/`)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 发送用户行为数据
|
|
5
|
+
* @param data - 用户行为数据
|
|
6
|
+
* @param url - 数据上报的URL
|
|
7
|
+
*/
|
|
8
|
+
var sendBehaviorData = function (data, url) {
|
|
9
|
+
if (navigator.sendBeacon) {
|
|
10
|
+
var blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
|
|
11
|
+
navigator.sendBeacon(url, blob);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
fetch(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
body: JSON.stringify(data),
|
|
18
|
+
}).catch(function (error) { return console.error('Error sending behavior data:', error); });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
var USER_ID_KEY = 'user_behavior_user_id';
|
|
23
|
+
var PV_COUNT_KEY = 'user_behavior_pv_count';
|
|
24
|
+
var UV_STORAGE_KEY = 'user_behavior_uv';
|
|
25
|
+
// 获取用户唯一标识(UUID)
|
|
26
|
+
var getUserID = function () {
|
|
27
|
+
var userId = localStorage.getItem(USER_ID_KEY);
|
|
28
|
+
if (!userId) {
|
|
29
|
+
userId = generateUUID();
|
|
30
|
+
localStorage.setItem(USER_ID_KEY, userId);
|
|
31
|
+
}
|
|
32
|
+
return userId;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* @description: 生成唯一标识符
|
|
36
|
+
* @return {string} 唯一标识符
|
|
37
|
+
*/
|
|
38
|
+
var generateUUID = function () {
|
|
39
|
+
return 'xxxx-xxxx-4xxx-yxxx-xxxx'.replace(/[xy]/g, function (char) {
|
|
40
|
+
var random = (Math.random() * 16) | 0;
|
|
41
|
+
var value = char === 'x' ? random : (random & 0x3) | 0x8;
|
|
42
|
+
return value.toString(16);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
var incrementPV = function () {
|
|
46
|
+
var today = new Date().toISOString().split('T')[0]; // 获取当天的日期
|
|
47
|
+
var pvData = localStorage.getItem("".concat(PV_COUNT_KEY, "_").concat(today));
|
|
48
|
+
var newPV = (pvData ? parseInt(pvData, 10) : 0) + 1;
|
|
49
|
+
localStorage.setItem("".concat(PV_COUNT_KEY, "_").concat(today), newPV.toString());
|
|
50
|
+
return newPV;
|
|
51
|
+
};
|
|
52
|
+
// 检查是否已经记录 UV
|
|
53
|
+
var isUVRecorded = function () {
|
|
54
|
+
var today = new Date().toISOString().split('T')[0];
|
|
55
|
+
return localStorage.getItem(UV_STORAGE_KEY) === today;
|
|
56
|
+
};
|
|
57
|
+
// 设置 UV 记录
|
|
58
|
+
var setUVRecorded = function () {
|
|
59
|
+
var today = new Date().toISOString().split('T')[0];
|
|
60
|
+
localStorage.setItem(UV_STORAGE_KEY, today);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 页面加载时间
|
|
64
|
+
var pageLoadTime = Date.now();
|
|
65
|
+
// 上一个页面的 URL
|
|
66
|
+
var lastPageUrl = window.location.href;
|
|
67
|
+
var lastDwellReportedForLoadTime = null;
|
|
68
|
+
/**
|
|
69
|
+
* @description: 跟踪用户行为(用户点击、MPA 首次加载 PV、通用停留时间、SPA 路由行为)
|
|
70
|
+
* @param projectName 项目名称
|
|
71
|
+
* @param reportUrl 上报 URL
|
|
72
|
+
*/
|
|
73
|
+
var trackUserBehavior = function (projectName, reportUrl) {
|
|
74
|
+
// 1. 通过事件委托捕获点击
|
|
75
|
+
trackClicks(projectName, reportUrl);
|
|
76
|
+
// 2. MPA 首次加载(页面初次进入)PV 上报
|
|
77
|
+
trackMpaPageView(projectName, reportUrl);
|
|
78
|
+
// 3. 通用停留时间(关闭页面/切换标签)上报
|
|
79
|
+
trackPageDwellTime(projectName, reportUrl);
|
|
80
|
+
// 4. SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)
|
|
81
|
+
trackSpaBehavior(projectName, reportUrl);
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* @description: 上报页面停留时间
|
|
85
|
+
* @param projectName 项目名称
|
|
86
|
+
* @param reportUrl 上报 URL
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
var reportDwellTime = function (projectName, reportUrl) {
|
|
90
|
+
if (lastDwellReportedForLoadTime === pageLoadTime)
|
|
91
|
+
return;
|
|
92
|
+
var dwellTime = Date.now() - pageLoadTime;
|
|
93
|
+
if (dwellTime > 0) {
|
|
94
|
+
sendBehaviorData({
|
|
95
|
+
behavior: 'dwell',
|
|
96
|
+
userId: getUserID(),
|
|
97
|
+
projectName: projectName,
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
pageUrl: lastPageUrl,
|
|
100
|
+
dwellTime: dwellTime,
|
|
101
|
+
}, reportUrl);
|
|
102
|
+
lastDwellReportedForLoadTime = pageLoadTime;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* 捕获首屏 PV(MPA传统网页)
|
|
107
|
+
* @param projectName 项目名称
|
|
108
|
+
* @param reportUrl 上报 URL
|
|
109
|
+
*/
|
|
110
|
+
var trackMpaPageView = function (projectName, reportUrl) {
|
|
111
|
+
window.addEventListener('load', function () {
|
|
112
|
+
var userId = getUserID();
|
|
113
|
+
var pv = incrementPV(); // 增加 PV 计数
|
|
114
|
+
// 发送 PV 数据
|
|
115
|
+
sendBehaviorData({
|
|
116
|
+
behavior: 'pv',
|
|
117
|
+
userId: userId,
|
|
118
|
+
projectName: projectName,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
pageUrl: window.location.href,
|
|
121
|
+
referrer: document.referrer || '', // 记录来源
|
|
122
|
+
pv: pv,
|
|
123
|
+
}, reportUrl);
|
|
124
|
+
// 记录页面加载时间
|
|
125
|
+
pageLoadTime = Date.now();
|
|
126
|
+
lastPageUrl = window.location.href;
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* @description: 捕获点击事件,通过 data-track-click 属性简化识别
|
|
131
|
+
* @param projectName 项目名称
|
|
132
|
+
* @param reportUrl 上报 URL
|
|
133
|
+
* @returns
|
|
134
|
+
*/
|
|
135
|
+
var trackClicks = function (projectName, reportUrl) {
|
|
136
|
+
document.addEventListener('click', function (event) {
|
|
137
|
+
var target = event.target;
|
|
138
|
+
// 如果目标元素带有 data-track-click 属性
|
|
139
|
+
if (target && target.dataset.trackClick) {
|
|
140
|
+
var behaviorData = {
|
|
141
|
+
behavior: 'click',
|
|
142
|
+
userId: getUserID(),
|
|
143
|
+
projectName: projectName,
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
145
|
+
element: target.tagName,
|
|
146
|
+
action: target.dataset.trackClick, // 自定义的点击行为
|
|
147
|
+
pageUrl: window.location.href,
|
|
148
|
+
referrer: lastPageUrl, // 记录点击时的页面来源
|
|
149
|
+
};
|
|
150
|
+
// 发送点击事件数据到服务端
|
|
151
|
+
sendBehaviorData(behaviorData, reportUrl);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* @description: 捕获页面停留时间(关闭/隐藏)
|
|
157
|
+
* @param projectName 项目名称
|
|
158
|
+
* @param reportUrl 上报 URL
|
|
159
|
+
* @returns
|
|
160
|
+
*/
|
|
161
|
+
var trackPageDwellTime = function (projectName, reportUrl) {
|
|
162
|
+
// 在 beforeunload 时计算停留时间
|
|
163
|
+
window.addEventListener('beforeunload', function () {
|
|
164
|
+
reportDwellTime(projectName, reportUrl);
|
|
165
|
+
});
|
|
166
|
+
window.addEventListener('pagehide', function () {
|
|
167
|
+
reportDwellTime(projectName, reportUrl);
|
|
168
|
+
});
|
|
169
|
+
// 在 visibilitychange(切换标签)时计算停留时间
|
|
170
|
+
document.addEventListener('visibilitychange', function () {
|
|
171
|
+
if (document.visibilityState === 'hidden') {
|
|
172
|
+
reportDwellTime(projectName, reportUrl);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* @description: 捕获 SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)
|
|
178
|
+
* @param projectName 项目名称
|
|
179
|
+
* @param reportUrl 上报 URL
|
|
180
|
+
* @returns
|
|
181
|
+
*/
|
|
182
|
+
var trackSpaBehavior = function (projectName, reportUrl) {
|
|
183
|
+
/**
|
|
184
|
+
* @description: 处理路由变化事件回调
|
|
185
|
+
* @returns
|
|
186
|
+
*/
|
|
187
|
+
var handleRouteChange = function () {
|
|
188
|
+
// 1. 防抖校验:如果 URL 没变(比如 hashchange 和 popstate 同时触发),直接退出
|
|
189
|
+
if (window.location.href === lastPageUrl)
|
|
190
|
+
return;
|
|
191
|
+
// 2. 结算上一页:上报前一个页面的停留时间
|
|
192
|
+
reportDwellTime(projectName, reportUrl);
|
|
193
|
+
// 3. 记录当前 URL 为 referrer (在更新 lastPageUrl 之前!)
|
|
194
|
+
var referrer = lastPageUrl;
|
|
195
|
+
// 4. 更新状态:保存当前 URL,为下一次跳转做准备
|
|
196
|
+
pageLoadTime = Date.now();
|
|
197
|
+
lastPageUrl = window.location.href;
|
|
198
|
+
// 5. 记录新页面:上报 PV
|
|
199
|
+
var userId = getUserID();
|
|
200
|
+
var pv = incrementPV();
|
|
201
|
+
sendBehaviorData({
|
|
202
|
+
behavior: 'pv',
|
|
203
|
+
userId: userId,
|
|
204
|
+
projectName: projectName,
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
pageUrl: window.location.href,
|
|
207
|
+
referrer: referrer, // 这里的 referrer 是跳转前的页面 URL
|
|
208
|
+
pv: pv,
|
|
209
|
+
}, reportUrl);
|
|
210
|
+
};
|
|
211
|
+
window.addEventListener('hashchange', handleRouteChange);
|
|
212
|
+
window.addEventListener('popstate', handleRouteChange);
|
|
213
|
+
var originalPush = history.pushState;
|
|
214
|
+
var originalReplace = history.replaceState;
|
|
215
|
+
history.pushState = function () {
|
|
216
|
+
var args = [];
|
|
217
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
218
|
+
args[_i] = arguments[_i];
|
|
219
|
+
}
|
|
220
|
+
originalPush.apply(this, args);
|
|
221
|
+
handleRouteChange();
|
|
222
|
+
};
|
|
223
|
+
history.replaceState = function () {
|
|
224
|
+
var args = [];
|
|
225
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
226
|
+
args[_i] = arguments[_i];
|
|
227
|
+
}
|
|
228
|
+
originalReplace.apply(this, args);
|
|
229
|
+
handleRouteChange();
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// 初始化用户行为监控
|
|
234
|
+
var initUserBehaviorMonitor = function (_a) {
|
|
235
|
+
var projectName = _a.projectName, reportUrl = _a.reportUrl;
|
|
236
|
+
var userId = getUserID();
|
|
237
|
+
// UV 统计:如果是用户首次访问,记录 UV
|
|
238
|
+
if (!isUVRecorded()) {
|
|
239
|
+
sendBehaviorData({
|
|
240
|
+
behavior: 'uv',
|
|
241
|
+
userId: userId,
|
|
242
|
+
projectName: projectName,
|
|
243
|
+
timestamp: new Date().toISOString(),
|
|
244
|
+
}, reportUrl);
|
|
245
|
+
setUVRecorded();
|
|
246
|
+
}
|
|
247
|
+
// 启动点击行为、PV 统计和页面停留时间监控
|
|
248
|
+
trackUserBehavior(projectName, reportUrl);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
exports.initUserBehaviorMonitor = initUserBehaviorMonitor;
|
|
252
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/sender.ts","../src/storage.ts","../src/tracker.ts","../src/index.ts"],"sourcesContent":["/**\r\n * 发送用户行为数据\r\n * @param data - 用户行为数据\r\n * @param url - 数据上报的URL\r\n */\r\nexport const sendBehaviorData = (data: Record<string, any>, url: string) => {\r\n if (navigator.sendBeacon) {\r\n const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });\r\n navigator.sendBeacon(url, blob);\r\n } else {\r\n fetch(url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data),\r\n }).catch((error) => console.error('Error sending behavior data:', error));\r\n }\r\n};","const USER_ID_KEY = 'user_behavior_user_id';\r\nconst PV_COUNT_KEY = 'user_behavior_pv_count';\r\nconst UV_STORAGE_KEY = 'user_behavior_uv';\r\n\r\n// 获取用户唯一标识(UUID)\r\nexport const getUserID = (): string => {\r\n let userId = localStorage.getItem(USER_ID_KEY);\r\n if (!userId) {\r\n userId = generateUUID();\r\n localStorage.setItem(USER_ID_KEY, userId);\r\n }\r\n return userId;\r\n};\r\n\r\n/**\r\n * @description: 生成唯一标识符\r\n * @return {string} 唯一标识符\r\n */\r\nconst generateUUID = (): string => {\r\n return 'xxxx-xxxx-4xxx-yxxx-xxxx'.replace(/[xy]/g, (char) => {\r\n const random = (Math.random() * 16) | 0;\r\n const value = char === 'x' ? random : (random & 0x3) | 0x8;\r\n return value.toString(16);\r\n });\r\n};\r\n\r\n\r\nexport const incrementPV = (): number => {\r\n const today = new Date().toISOString().split('T')[0]; // 获取当天的日期\r\n const pvData = localStorage.getItem(`${PV_COUNT_KEY}_${today}`);\r\n const newPV = (pvData ? parseInt(pvData, 10) : 0) + 1;\r\n localStorage.setItem(`${PV_COUNT_KEY}_${today}`, newPV.toString());\r\n return newPV;\r\n};\r\n\r\n\r\n// 检查是否已经记录 UV\r\nexport const isUVRecorded = (): boolean => {\r\n const today = new Date().toISOString().split('T')[0];\r\n return localStorage.getItem(UV_STORAGE_KEY) === today;\r\n};\r\n\r\n\r\n// 设置 UV 记录\r\nexport const setUVRecorded = () => {\r\n const today = new Date().toISOString().split('T')[0];\r\n localStorage.setItem(UV_STORAGE_KEY, today);\r\n};","import { sendBehaviorData } from './sender';\r\nimport { getUserID, incrementPV } from './storage';\r\n\r\n// 页面加载时间\r\nlet pageLoadTime: number = Date.now();\r\n// 上一个页面的 URL\r\nlet lastPageUrl: string = window.location.href;\r\nlet lastDwellReportedForLoadTime: number | null = null;\r\n\r\n\r\n/**\r\n * @description: 跟踪用户行为(用户点击、MPA 首次加载 PV、通用停留时间、SPA 路由行为)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n */\r\nexport const trackUserBehavior = (projectName: string, reportUrl: string) => {\r\n // 1. 通过事件委托捕获点击\r\n trackClicks(projectName, reportUrl);\r\n\r\n // 2. MPA 首次加载(页面初次进入)PV 上报\r\n trackMpaPageView(projectName, reportUrl);\r\n\r\n // 3. 通用停留时间(关闭页面/切换标签)上报\r\n trackPageDwellTime(projectName, reportUrl);\r\n\r\n // 4. SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)\r\n trackSpaBehavior(projectName, reportUrl);\r\n};\r\n\r\n/**\r\n * @description: 上报页面停留时间\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst reportDwellTime = (projectName: string, reportUrl: string) => {\r\n if (lastDwellReportedForLoadTime === pageLoadTime) return;\r\n const dwellTime = Date.now() - pageLoadTime;\r\n if (dwellTime > 0) {\r\n sendBehaviorData({\r\n behavior: 'dwell',\r\n userId: getUserID(),\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: lastPageUrl,\r\n dwellTime,\r\n }, reportUrl);\r\n lastDwellReportedForLoadTime = pageLoadTime;\r\n }\r\n};\r\n\r\n\r\n/**\r\n * 捕获首屏 PV(MPA传统网页)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n */\r\nconst trackMpaPageView = (projectName: string, reportUrl: string) => {\r\n window.addEventListener('load', () => {\r\n const userId = getUserID();\r\n const pv = incrementPV(); // 增加 PV 计数\r\n\r\n // 发送 PV 数据\r\n sendBehaviorData({\r\n behavior: 'pv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: window.location.href,\r\n referrer: document.referrer || '', // 记录来源\r\n pv,\r\n }, reportUrl);\r\n\r\n // 记录页面加载时间\r\n pageLoadTime = Date.now();\r\n lastPageUrl = window.location.href;\r\n });\r\n};\r\n\r\n\r\n/**\r\n * @description: 捕获点击事件,通过 data-track-click 属性简化识别\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackClicks = (projectName: string, reportUrl: string) => {\r\n document.addEventListener('click', (event) => {\r\n const target = event.target as HTMLElement;\r\n\r\n // 如果目标元素带有 data-track-click 属性\r\n if (target && target.dataset.trackClick) {\r\n const behaviorData = {\r\n behavior: 'click',\r\n userId: getUserID(),\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n element: target.tagName,\r\n action: target.dataset.trackClick, // 自定义的点击行为\r\n pageUrl: window.location.href,\r\n referrer: lastPageUrl, // 记录点击时的页面来源\r\n };\r\n\r\n // 发送点击事件数据到服务端\r\n sendBehaviorData(behaviorData, reportUrl);\r\n }\r\n });\r\n};\r\n\r\n\r\n\r\n/**\r\n * @description: 捕获页面停留时间(关闭/隐藏)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackPageDwellTime = (projectName: string, reportUrl: string) => {\r\n // 在 beforeunload 时计算停留时间\r\n window.addEventListener('beforeunload', () => {\r\n reportDwellTime(projectName, reportUrl);\r\n });\r\n\r\n window.addEventListener('pagehide', () => {\r\n reportDwellTime(projectName, reportUrl);\r\n });\r\n\r\n // 在 visibilitychange(切换标签)时计算停留时间\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'hidden') {\r\n reportDwellTime(projectName, reportUrl);\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * @description: 捕获 SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackSpaBehavior = (projectName: string, reportUrl: string) => {\r\n /**\r\n * @description: 处理路由变化事件回调\r\n * @returns \r\n */\r\n const handleRouteChange = () => {\r\n // 1. 防抖校验:如果 URL 没变(比如 hashchange 和 popstate 同时触发),直接退出\r\n if (window.location.href === lastPageUrl) return;\r\n\r\n // 2. 结算上一页:上报前一个页面的停留时间\r\n reportDwellTime(projectName, reportUrl);\r\n\r\n // 3. 记录当前 URL 为 referrer (在更新 lastPageUrl 之前!)\r\n const referrer = lastPageUrl;\r\n\r\n // 4. 更新状态:保存当前 URL,为下一次跳转做准备\r\n pageLoadTime = Date.now();\r\n lastPageUrl = window.location.href;\r\n\r\n // 5. 记录新页面:上报 PV\r\n const userId = getUserID();\r\n const pv = incrementPV();\r\n sendBehaviorData({\r\n behavior: 'pv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: window.location.href,\r\n referrer: referrer, // 这里的 referrer 是跳转前的页面 URL\r\n pv,\r\n }, reportUrl);\r\n };\r\n\r\n window.addEventListener('hashchange', handleRouteChange);\r\n window.addEventListener('popstate', handleRouteChange);\r\n\r\n const originalPush = history.pushState;\r\n const originalReplace = history.replaceState;\r\n\r\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\r\n originalPush.apply(this, args);\r\n handleRouteChange();\r\n };\r\n\r\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\r\n originalReplace.apply(this, args);\r\n handleRouteChange();\r\n };\r\n};","\r\nimport { trackUserBehavior } from './tracker';\r\nimport { getUserID, isUVRecorded, setUVRecorded } from './storage';\r\nimport { sendBehaviorData } from './sender';\r\n\r\ninterface MonitorConfig {\r\n projectName: string; // 当前项目名称,用于区分项目\r\n reportUrl: string; // 上报服务器的地址\r\n}\r\n\r\n// 初始化用户行为监控\r\nexport const initUserBehaviorMonitor = ({ projectName, reportUrl }: MonitorConfig) => {\r\n const userId = getUserID();\r\n\r\n // UV 统计:如果是用户首次访问,记录 UV\r\n if (!isUVRecorded()) {\r\n sendBehaviorData({\r\n behavior: 'uv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n }, reportUrl);\r\n setUVRecorded();\r\n }\r\n\r\n // 启动点击行为、PV 统计和页面停留时间监控\r\n trackUserBehavior(projectName, reportUrl);\r\n};"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发送用户行为数据
|
|
3
|
+
* @param data - 用户行为数据
|
|
4
|
+
* @param url - 数据上报的URL
|
|
5
|
+
*/
|
|
6
|
+
var sendBehaviorData = function (data, url) {
|
|
7
|
+
if (navigator.sendBeacon) {
|
|
8
|
+
var blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
|
|
9
|
+
navigator.sendBeacon(url, blob);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
fetch(url, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
body: JSON.stringify(data),
|
|
16
|
+
}).catch(function (error) { return console.error('Error sending behavior data:', error); });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
var USER_ID_KEY = 'user_behavior_user_id';
|
|
21
|
+
var PV_COUNT_KEY = 'user_behavior_pv_count';
|
|
22
|
+
var UV_STORAGE_KEY = 'user_behavior_uv';
|
|
23
|
+
// 获取用户唯一标识(UUID)
|
|
24
|
+
var getUserID = function () {
|
|
25
|
+
var userId = localStorage.getItem(USER_ID_KEY);
|
|
26
|
+
if (!userId) {
|
|
27
|
+
userId = generateUUID();
|
|
28
|
+
localStorage.setItem(USER_ID_KEY, userId);
|
|
29
|
+
}
|
|
30
|
+
return userId;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* @description: 生成唯一标识符
|
|
34
|
+
* @return {string} 唯一标识符
|
|
35
|
+
*/
|
|
36
|
+
var generateUUID = function () {
|
|
37
|
+
return 'xxxx-xxxx-4xxx-yxxx-xxxx'.replace(/[xy]/g, function (char) {
|
|
38
|
+
var random = (Math.random() * 16) | 0;
|
|
39
|
+
var value = char === 'x' ? random : (random & 0x3) | 0x8;
|
|
40
|
+
return value.toString(16);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
var incrementPV = function () {
|
|
44
|
+
var today = new Date().toISOString().split('T')[0]; // 获取当天的日期
|
|
45
|
+
var pvData = localStorage.getItem("".concat(PV_COUNT_KEY, "_").concat(today));
|
|
46
|
+
var newPV = (pvData ? parseInt(pvData, 10) : 0) + 1;
|
|
47
|
+
localStorage.setItem("".concat(PV_COUNT_KEY, "_").concat(today), newPV.toString());
|
|
48
|
+
return newPV;
|
|
49
|
+
};
|
|
50
|
+
// 检查是否已经记录 UV
|
|
51
|
+
var isUVRecorded = function () {
|
|
52
|
+
var today = new Date().toISOString().split('T')[0];
|
|
53
|
+
return localStorage.getItem(UV_STORAGE_KEY) === today;
|
|
54
|
+
};
|
|
55
|
+
// 设置 UV 记录
|
|
56
|
+
var setUVRecorded = function () {
|
|
57
|
+
var today = new Date().toISOString().split('T')[0];
|
|
58
|
+
localStorage.setItem(UV_STORAGE_KEY, today);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 页面加载时间
|
|
62
|
+
var pageLoadTime = Date.now();
|
|
63
|
+
// 上一个页面的 URL
|
|
64
|
+
var lastPageUrl = window.location.href;
|
|
65
|
+
var lastDwellReportedForLoadTime = null;
|
|
66
|
+
/**
|
|
67
|
+
* @description: 跟踪用户行为(用户点击、MPA 首次加载 PV、通用停留时间、SPA 路由行为)
|
|
68
|
+
* @param projectName 项目名称
|
|
69
|
+
* @param reportUrl 上报 URL
|
|
70
|
+
*/
|
|
71
|
+
var trackUserBehavior = function (projectName, reportUrl) {
|
|
72
|
+
// 1. 通过事件委托捕获点击
|
|
73
|
+
trackClicks(projectName, reportUrl);
|
|
74
|
+
// 2. MPA 首次加载(页面初次进入)PV 上报
|
|
75
|
+
trackMpaPageView(projectName, reportUrl);
|
|
76
|
+
// 3. 通用停留时间(关闭页面/切换标签)上报
|
|
77
|
+
trackPageDwellTime(projectName, reportUrl);
|
|
78
|
+
// 4. SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)
|
|
79
|
+
trackSpaBehavior(projectName, reportUrl);
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* @description: 上报页面停留时间
|
|
83
|
+
* @param projectName 项目名称
|
|
84
|
+
* @param reportUrl 上报 URL
|
|
85
|
+
* @returns
|
|
86
|
+
*/
|
|
87
|
+
var reportDwellTime = function (projectName, reportUrl) {
|
|
88
|
+
if (lastDwellReportedForLoadTime === pageLoadTime)
|
|
89
|
+
return;
|
|
90
|
+
var dwellTime = Date.now() - pageLoadTime;
|
|
91
|
+
if (dwellTime > 0) {
|
|
92
|
+
sendBehaviorData({
|
|
93
|
+
behavior: 'dwell',
|
|
94
|
+
userId: getUserID(),
|
|
95
|
+
projectName: projectName,
|
|
96
|
+
timestamp: new Date().toISOString(),
|
|
97
|
+
pageUrl: lastPageUrl,
|
|
98
|
+
dwellTime: dwellTime,
|
|
99
|
+
}, reportUrl);
|
|
100
|
+
lastDwellReportedForLoadTime = pageLoadTime;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* 捕获首屏 PV(MPA传统网页)
|
|
105
|
+
* @param projectName 项目名称
|
|
106
|
+
* @param reportUrl 上报 URL
|
|
107
|
+
*/
|
|
108
|
+
var trackMpaPageView = function (projectName, reportUrl) {
|
|
109
|
+
window.addEventListener('load', function () {
|
|
110
|
+
var userId = getUserID();
|
|
111
|
+
var pv = incrementPV(); // 增加 PV 计数
|
|
112
|
+
// 发送 PV 数据
|
|
113
|
+
sendBehaviorData({
|
|
114
|
+
behavior: 'pv',
|
|
115
|
+
userId: userId,
|
|
116
|
+
projectName: projectName,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
pageUrl: window.location.href,
|
|
119
|
+
referrer: document.referrer || '', // 记录来源
|
|
120
|
+
pv: pv,
|
|
121
|
+
}, reportUrl);
|
|
122
|
+
// 记录页面加载时间
|
|
123
|
+
pageLoadTime = Date.now();
|
|
124
|
+
lastPageUrl = window.location.href;
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* @description: 捕获点击事件,通过 data-track-click 属性简化识别
|
|
129
|
+
* @param projectName 项目名称
|
|
130
|
+
* @param reportUrl 上报 URL
|
|
131
|
+
* @returns
|
|
132
|
+
*/
|
|
133
|
+
var trackClicks = function (projectName, reportUrl) {
|
|
134
|
+
document.addEventListener('click', function (event) {
|
|
135
|
+
var target = event.target;
|
|
136
|
+
// 如果目标元素带有 data-track-click 属性
|
|
137
|
+
if (target && target.dataset.trackClick) {
|
|
138
|
+
var behaviorData = {
|
|
139
|
+
behavior: 'click',
|
|
140
|
+
userId: getUserID(),
|
|
141
|
+
projectName: projectName,
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
element: target.tagName,
|
|
144
|
+
action: target.dataset.trackClick, // 自定义的点击行为
|
|
145
|
+
pageUrl: window.location.href,
|
|
146
|
+
referrer: lastPageUrl, // 记录点击时的页面来源
|
|
147
|
+
};
|
|
148
|
+
// 发送点击事件数据到服务端
|
|
149
|
+
sendBehaviorData(behaviorData, reportUrl);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* @description: 捕获页面停留时间(关闭/隐藏)
|
|
155
|
+
* @param projectName 项目名称
|
|
156
|
+
* @param reportUrl 上报 URL
|
|
157
|
+
* @returns
|
|
158
|
+
*/
|
|
159
|
+
var trackPageDwellTime = function (projectName, reportUrl) {
|
|
160
|
+
// 在 beforeunload 时计算停留时间
|
|
161
|
+
window.addEventListener('beforeunload', function () {
|
|
162
|
+
reportDwellTime(projectName, reportUrl);
|
|
163
|
+
});
|
|
164
|
+
window.addEventListener('pagehide', function () {
|
|
165
|
+
reportDwellTime(projectName, reportUrl);
|
|
166
|
+
});
|
|
167
|
+
// 在 visibilitychange(切换标签)时计算停留时间
|
|
168
|
+
document.addEventListener('visibilitychange', function () {
|
|
169
|
+
if (document.visibilityState === 'hidden') {
|
|
170
|
+
reportDwellTime(projectName, reportUrl);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* @description: 捕获 SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)
|
|
176
|
+
* @param projectName 项目名称
|
|
177
|
+
* @param reportUrl 上报 URL
|
|
178
|
+
* @returns
|
|
179
|
+
*/
|
|
180
|
+
var trackSpaBehavior = function (projectName, reportUrl) {
|
|
181
|
+
/**
|
|
182
|
+
* @description: 处理路由变化事件回调
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
var handleRouteChange = function () {
|
|
186
|
+
// 1. 防抖校验:如果 URL 没变(比如 hashchange 和 popstate 同时触发),直接退出
|
|
187
|
+
if (window.location.href === lastPageUrl)
|
|
188
|
+
return;
|
|
189
|
+
// 2. 结算上一页:上报前一个页面的停留时间
|
|
190
|
+
reportDwellTime(projectName, reportUrl);
|
|
191
|
+
// 3. 记录当前 URL 为 referrer (在更新 lastPageUrl 之前!)
|
|
192
|
+
var referrer = lastPageUrl;
|
|
193
|
+
// 4. 更新状态:保存当前 URL,为下一次跳转做准备
|
|
194
|
+
pageLoadTime = Date.now();
|
|
195
|
+
lastPageUrl = window.location.href;
|
|
196
|
+
// 5. 记录新页面:上报 PV
|
|
197
|
+
var userId = getUserID();
|
|
198
|
+
var pv = incrementPV();
|
|
199
|
+
sendBehaviorData({
|
|
200
|
+
behavior: 'pv',
|
|
201
|
+
userId: userId,
|
|
202
|
+
projectName: projectName,
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
pageUrl: window.location.href,
|
|
205
|
+
referrer: referrer, // 这里的 referrer 是跳转前的页面 URL
|
|
206
|
+
pv: pv,
|
|
207
|
+
}, reportUrl);
|
|
208
|
+
};
|
|
209
|
+
window.addEventListener('hashchange', handleRouteChange);
|
|
210
|
+
window.addEventListener('popstate', handleRouteChange);
|
|
211
|
+
var originalPush = history.pushState;
|
|
212
|
+
var originalReplace = history.replaceState;
|
|
213
|
+
history.pushState = function () {
|
|
214
|
+
var args = [];
|
|
215
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
216
|
+
args[_i] = arguments[_i];
|
|
217
|
+
}
|
|
218
|
+
originalPush.apply(this, args);
|
|
219
|
+
handleRouteChange();
|
|
220
|
+
};
|
|
221
|
+
history.replaceState = function () {
|
|
222
|
+
var args = [];
|
|
223
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
224
|
+
args[_i] = arguments[_i];
|
|
225
|
+
}
|
|
226
|
+
originalReplace.apply(this, args);
|
|
227
|
+
handleRouteChange();
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// 初始化用户行为监控
|
|
232
|
+
var initUserBehaviorMonitor = function (_a) {
|
|
233
|
+
var projectName = _a.projectName, reportUrl = _a.reportUrl;
|
|
234
|
+
var userId = getUserID();
|
|
235
|
+
// UV 统计:如果是用户首次访问,记录 UV
|
|
236
|
+
if (!isUVRecorded()) {
|
|
237
|
+
sendBehaviorData({
|
|
238
|
+
behavior: 'uv',
|
|
239
|
+
userId: userId,
|
|
240
|
+
projectName: projectName,
|
|
241
|
+
timestamp: new Date().toISOString(),
|
|
242
|
+
}, reportUrl);
|
|
243
|
+
setUVRecorded();
|
|
244
|
+
}
|
|
245
|
+
// 启动点击行为、PV 统计和页面停留时间监控
|
|
246
|
+
trackUserBehavior(projectName, reportUrl);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export { initUserBehaviorMonitor };
|
|
250
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/sender.ts","../src/storage.ts","../src/tracker.ts","../src/index.ts"],"sourcesContent":["/**\r\n * 发送用户行为数据\r\n * @param data - 用户行为数据\r\n * @param url - 数据上报的URL\r\n */\r\nexport const sendBehaviorData = (data: Record<string, any>, url: string) => {\r\n if (navigator.sendBeacon) {\r\n const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });\r\n navigator.sendBeacon(url, blob);\r\n } else {\r\n fetch(url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data),\r\n }).catch((error) => console.error('Error sending behavior data:', error));\r\n }\r\n};","const USER_ID_KEY = 'user_behavior_user_id';\r\nconst PV_COUNT_KEY = 'user_behavior_pv_count';\r\nconst UV_STORAGE_KEY = 'user_behavior_uv';\r\n\r\n// 获取用户唯一标识(UUID)\r\nexport const getUserID = (): string => {\r\n let userId = localStorage.getItem(USER_ID_KEY);\r\n if (!userId) {\r\n userId = generateUUID();\r\n localStorage.setItem(USER_ID_KEY, userId);\r\n }\r\n return userId;\r\n};\r\n\r\n/**\r\n * @description: 生成唯一标识符\r\n * @return {string} 唯一标识符\r\n */\r\nconst generateUUID = (): string => {\r\n return 'xxxx-xxxx-4xxx-yxxx-xxxx'.replace(/[xy]/g, (char) => {\r\n const random = (Math.random() * 16) | 0;\r\n const value = char === 'x' ? random : (random & 0x3) | 0x8;\r\n return value.toString(16);\r\n });\r\n};\r\n\r\n\r\nexport const incrementPV = (): number => {\r\n const today = new Date().toISOString().split('T')[0]; // 获取当天的日期\r\n const pvData = localStorage.getItem(`${PV_COUNT_KEY}_${today}`);\r\n const newPV = (pvData ? parseInt(pvData, 10) : 0) + 1;\r\n localStorage.setItem(`${PV_COUNT_KEY}_${today}`, newPV.toString());\r\n return newPV;\r\n};\r\n\r\n\r\n// 检查是否已经记录 UV\r\nexport const isUVRecorded = (): boolean => {\r\n const today = new Date().toISOString().split('T')[0];\r\n return localStorage.getItem(UV_STORAGE_KEY) === today;\r\n};\r\n\r\n\r\n// 设置 UV 记录\r\nexport const setUVRecorded = () => {\r\n const today = new Date().toISOString().split('T')[0];\r\n localStorage.setItem(UV_STORAGE_KEY, today);\r\n};","import { sendBehaviorData } from './sender';\r\nimport { getUserID, incrementPV } from './storage';\r\n\r\n// 页面加载时间\r\nlet pageLoadTime: number = Date.now();\r\n// 上一个页面的 URL\r\nlet lastPageUrl: string = window.location.href;\r\nlet lastDwellReportedForLoadTime: number | null = null;\r\n\r\n\r\n/**\r\n * @description: 跟踪用户行为(用户点击、MPA 首次加载 PV、通用停留时间、SPA 路由行为)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n */\r\nexport const trackUserBehavior = (projectName: string, reportUrl: string) => {\r\n // 1. 通过事件委托捕获点击\r\n trackClicks(projectName, reportUrl);\r\n\r\n // 2. MPA 首次加载(页面初次进入)PV 上报\r\n trackMpaPageView(projectName, reportUrl);\r\n\r\n // 3. 通用停留时间(关闭页面/切换标签)上报\r\n trackPageDwellTime(projectName, reportUrl);\r\n\r\n // 4. SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)\r\n trackSpaBehavior(projectName, reportUrl);\r\n};\r\n\r\n/**\r\n * @description: 上报页面停留时间\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst reportDwellTime = (projectName: string, reportUrl: string) => {\r\n if (lastDwellReportedForLoadTime === pageLoadTime) return;\r\n const dwellTime = Date.now() - pageLoadTime;\r\n if (dwellTime > 0) {\r\n sendBehaviorData({\r\n behavior: 'dwell',\r\n userId: getUserID(),\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: lastPageUrl,\r\n dwellTime,\r\n }, reportUrl);\r\n lastDwellReportedForLoadTime = pageLoadTime;\r\n }\r\n};\r\n\r\n\r\n/**\r\n * 捕获首屏 PV(MPA传统网页)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n */\r\nconst trackMpaPageView = (projectName: string, reportUrl: string) => {\r\n window.addEventListener('load', () => {\r\n const userId = getUserID();\r\n const pv = incrementPV(); // 增加 PV 计数\r\n\r\n // 发送 PV 数据\r\n sendBehaviorData({\r\n behavior: 'pv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: window.location.href,\r\n referrer: document.referrer || '', // 记录来源\r\n pv,\r\n }, reportUrl);\r\n\r\n // 记录页面加载时间\r\n pageLoadTime = Date.now();\r\n lastPageUrl = window.location.href;\r\n });\r\n};\r\n\r\n\r\n/**\r\n * @description: 捕获点击事件,通过 data-track-click 属性简化识别\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackClicks = (projectName: string, reportUrl: string) => {\r\n document.addEventListener('click', (event) => {\r\n const target = event.target as HTMLElement;\r\n\r\n // 如果目标元素带有 data-track-click 属性\r\n if (target && target.dataset.trackClick) {\r\n const behaviorData = {\r\n behavior: 'click',\r\n userId: getUserID(),\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n element: target.tagName,\r\n action: target.dataset.trackClick, // 自定义的点击行为\r\n pageUrl: window.location.href,\r\n referrer: lastPageUrl, // 记录点击时的页面来源\r\n };\r\n\r\n // 发送点击事件数据到服务端\r\n sendBehaviorData(behaviorData, reportUrl);\r\n }\r\n });\r\n};\r\n\r\n\r\n\r\n/**\r\n * @description: 捕获页面停留时间(关闭/隐藏)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackPageDwellTime = (projectName: string, reportUrl: string) => {\r\n // 在 beforeunload 时计算停留时间\r\n window.addEventListener('beforeunload', () => {\r\n reportDwellTime(projectName, reportUrl);\r\n });\r\n\r\n window.addEventListener('pagehide', () => {\r\n reportDwellTime(projectName, reportUrl);\r\n });\r\n\r\n // 在 visibilitychange(切换标签)时计算停留时间\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'hidden') {\r\n reportDwellTime(projectName, reportUrl);\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * @description: 捕获 SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackSpaBehavior = (projectName: string, reportUrl: string) => {\r\n /**\r\n * @description: 处理路由变化事件回调\r\n * @returns \r\n */\r\n const handleRouteChange = () => {\r\n // 1. 防抖校验:如果 URL 没变(比如 hashchange 和 popstate 同时触发),直接退出\r\n if (window.location.href === lastPageUrl) return;\r\n\r\n // 2. 结算上一页:上报前一个页面的停留时间\r\n reportDwellTime(projectName, reportUrl);\r\n\r\n // 3. 记录当前 URL 为 referrer (在更新 lastPageUrl 之前!)\r\n const referrer = lastPageUrl;\r\n\r\n // 4. 更新状态:保存当前 URL,为下一次跳转做准备\r\n pageLoadTime = Date.now();\r\n lastPageUrl = window.location.href;\r\n\r\n // 5. 记录新页面:上报 PV\r\n const userId = getUserID();\r\n const pv = incrementPV();\r\n sendBehaviorData({\r\n behavior: 'pv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: window.location.href,\r\n referrer: referrer, // 这里的 referrer 是跳转前的页面 URL\r\n pv,\r\n }, reportUrl);\r\n };\r\n\r\n window.addEventListener('hashchange', handleRouteChange);\r\n window.addEventListener('popstate', handleRouteChange);\r\n\r\n const originalPush = history.pushState;\r\n const originalReplace = history.replaceState;\r\n\r\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\r\n originalPush.apply(this, args);\r\n handleRouteChange();\r\n };\r\n\r\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\r\n originalReplace.apply(this, args);\r\n handleRouteChange();\r\n };\r\n};","\r\nimport { trackUserBehavior } from './tracker';\r\nimport { getUserID, isUVRecorded, setUVRecorded } from './storage';\r\nimport { sendBehaviorData } from './sender';\r\n\r\ninterface MonitorConfig {\r\n projectName: string; // 当前项目名称,用于区分项目\r\n reportUrl: string; // 上报服务器的地址\r\n}\r\n\r\n// 初始化用户行为监控\r\nexport const initUserBehaviorMonitor = ({ projectName, reportUrl }: MonitorConfig) => {\r\n const userId = getUserID();\r\n\r\n // UV 统计:如果是用户首次访问,记录 UV\r\n if (!isUVRecorded()) {\r\n sendBehaviorData({\r\n behavior: 'uv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n }, reportUrl);\r\n setUVRecorded();\r\n }\r\n\r\n // 启动点击行为、PV 统计和页面停留时间监控\r\n trackUserBehavior(projectName, reportUrl);\r\n};"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).frontendBehaviorMonitor={})}(this,function(e){"use strict";var t=function(e,t){if(navigator.sendBeacon){var n=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(t,n)}else fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}).catch(function(e){return console.error("Error sending behavior data:",e)})},n="user_behavior_user_id",o="user_behavior_pv_count",r="user_behavior_uv",a=function(){var e=localStorage.getItem(n);return e||(e=i(),localStorage.setItem(n,e)),e},i=function(){return"xxxx-xxxx-4xxx-yxxx-xxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})},c=function(){var e=(new Date).toISOString().split("T")[0],t=localStorage.getItem("".concat(o,"_").concat(e)),n=(t?parseInt(t,10):0)+1;return localStorage.setItem("".concat(o,"_").concat(e),n.toString()),n},d=Date.now(),s=window.location.href,l=null,f=function(e,n){if(l!==d){var o=Date.now()-d;o>0&&(t({behavior:"dwell",userId:a(),projectName:e,timestamp:(new Date).toISOString(),pageUrl:s,dwellTime:o},n),l=d)}},u=function(e,n){window.addEventListener("load",function(){var o=a(),r=c();t({behavior:"pv",userId:o,projectName:e,timestamp:(new Date).toISOString(),pageUrl:window.location.href,referrer:document.referrer||"",pv:r},n),d=Date.now(),s=window.location.href})},p=function(e,n){document.addEventListener("click",function(o){var r=o.target;if(r&&r.dataset.trackClick){var i={behavior:"click",userId:a(),projectName:e,timestamp:(new Date).toISOString(),element:r.tagName,action:r.dataset.trackClick,pageUrl:window.location.href,referrer:s};t(i,n)}})},v=function(e,t){window.addEventListener("beforeunload",function(){f(e,t)}),window.addEventListener("pagehide",function(){f(e,t)}),document.addEventListener("visibilitychange",function(){"hidden"===document.visibilityState&&f(e,t)})},h=function(e,n){var o=function(){if(window.location.href!==s){f(e,n);var o=s;d=Date.now(),s=window.location.href;var r=a(),i=c();t({behavior:"pv",userId:r,projectName:e,timestamp:(new Date).toISOString(),pageUrl:window.location.href,referrer:o,pv:i},n)}};window.addEventListener("hashchange",o),window.addEventListener("popstate",o);var r=history.pushState,i=history.replaceState;history.pushState=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];r.apply(this,e),o()},history.replaceState=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];i.apply(this,e),o()}};e.initUserBehaviorMonitor=function(e){var n,o=e.projectName,i=e.reportUrl,c=a();n=(new Date).toISOString().split("T")[0],localStorage.getItem(r)!==n&&(t({behavior:"uv",userId:c,projectName:o,timestamp:(new Date).toISOString()},i),function(){var e=(new Date).toISOString().split("T")[0];localStorage.setItem(r,e)}()),function(e,t){p(e,t),u(e,t),v(e,t),h(e,t)}(o,i)}});
|
|
2
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/sender.ts","../src/storage.ts","../src/tracker.ts","../src/index.ts"],"sourcesContent":["/**\r\n * 发送用户行为数据\r\n * @param data - 用户行为数据\r\n * @param url - 数据上报的URL\r\n */\r\nexport const sendBehaviorData = (data: Record<string, any>, url: string) => {\r\n if (navigator.sendBeacon) {\r\n const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });\r\n navigator.sendBeacon(url, blob);\r\n } else {\r\n fetch(url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data),\r\n }).catch((error) => console.error('Error sending behavior data:', error));\r\n }\r\n};","const USER_ID_KEY = 'user_behavior_user_id';\r\nconst PV_COUNT_KEY = 'user_behavior_pv_count';\r\nconst UV_STORAGE_KEY = 'user_behavior_uv';\r\n\r\n// 获取用户唯一标识(UUID)\r\nexport const getUserID = (): string => {\r\n let userId = localStorage.getItem(USER_ID_KEY);\r\n if (!userId) {\r\n userId = generateUUID();\r\n localStorage.setItem(USER_ID_KEY, userId);\r\n }\r\n return userId;\r\n};\r\n\r\n/**\r\n * @description: 生成唯一标识符\r\n * @return {string} 唯一标识符\r\n */\r\nconst generateUUID = (): string => {\r\n return 'xxxx-xxxx-4xxx-yxxx-xxxx'.replace(/[xy]/g, (char) => {\r\n const random = (Math.random() * 16) | 0;\r\n const value = char === 'x' ? random : (random & 0x3) | 0x8;\r\n return value.toString(16);\r\n });\r\n};\r\n\r\n\r\nexport const incrementPV = (): number => {\r\n const today = new Date().toISOString().split('T')[0]; // 获取当天的日期\r\n const pvData = localStorage.getItem(`${PV_COUNT_KEY}_${today}`);\r\n const newPV = (pvData ? parseInt(pvData, 10) : 0) + 1;\r\n localStorage.setItem(`${PV_COUNT_KEY}_${today}`, newPV.toString());\r\n return newPV;\r\n};\r\n\r\n\r\n// 检查是否已经记录 UV\r\nexport const isUVRecorded = (): boolean => {\r\n const today = new Date().toISOString().split('T')[0];\r\n return localStorage.getItem(UV_STORAGE_KEY) === today;\r\n};\r\n\r\n\r\n// 设置 UV 记录\r\nexport const setUVRecorded = () => {\r\n const today = new Date().toISOString().split('T')[0];\r\n localStorage.setItem(UV_STORAGE_KEY, today);\r\n};","import { sendBehaviorData } from './sender';\r\nimport { getUserID, incrementPV } from './storage';\r\n\r\n// 页面加载时间\r\nlet pageLoadTime: number = Date.now();\r\n// 上一个页面的 URL\r\nlet lastPageUrl: string = window.location.href;\r\nlet lastDwellReportedForLoadTime: number | null = null;\r\n\r\n\r\n/**\r\n * @description: 跟踪用户行为(用户点击、MPA 首次加载 PV、通用停留时间、SPA 路由行为)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n */\r\nexport const trackUserBehavior = (projectName: string, reportUrl: string) => {\r\n // 1. 通过事件委托捕获点击\r\n trackClicks(projectName, reportUrl);\r\n\r\n // 2. MPA 首次加载(页面初次进入)PV 上报\r\n trackMpaPageView(projectName, reportUrl);\r\n\r\n // 3. 通用停留时间(关闭页面/切换标签)上报\r\n trackPageDwellTime(projectName, reportUrl);\r\n\r\n // 4. SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)\r\n trackSpaBehavior(projectName, reportUrl);\r\n};\r\n\r\n/**\r\n * @description: 上报页面停留时间\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst reportDwellTime = (projectName: string, reportUrl: string) => {\r\n if (lastDwellReportedForLoadTime === pageLoadTime) return;\r\n const dwellTime = Date.now() - pageLoadTime;\r\n if (dwellTime > 0) {\r\n sendBehaviorData({\r\n behavior: 'dwell',\r\n userId: getUserID(),\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: lastPageUrl,\r\n dwellTime,\r\n }, reportUrl);\r\n lastDwellReportedForLoadTime = pageLoadTime;\r\n }\r\n};\r\n\r\n\r\n/**\r\n * 捕获首屏 PV(MPA传统网页)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n */\r\nconst trackMpaPageView = (projectName: string, reportUrl: string) => {\r\n window.addEventListener('load', () => {\r\n const userId = getUserID();\r\n const pv = incrementPV(); // 增加 PV 计数\r\n\r\n // 发送 PV 数据\r\n sendBehaviorData({\r\n behavior: 'pv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: window.location.href,\r\n referrer: document.referrer || '', // 记录来源\r\n pv,\r\n }, reportUrl);\r\n\r\n // 记录页面加载时间\r\n pageLoadTime = Date.now();\r\n lastPageUrl = window.location.href;\r\n });\r\n};\r\n\r\n\r\n/**\r\n * @description: 捕获点击事件,通过 data-track-click 属性简化识别\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackClicks = (projectName: string, reportUrl: string) => {\r\n document.addEventListener('click', (event) => {\r\n const target = event.target as HTMLElement;\r\n\r\n // 如果目标元素带有 data-track-click 属性\r\n if (target && target.dataset.trackClick) {\r\n const behaviorData = {\r\n behavior: 'click',\r\n userId: getUserID(),\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n element: target.tagName,\r\n action: target.dataset.trackClick, // 自定义的点击行为\r\n pageUrl: window.location.href,\r\n referrer: lastPageUrl, // 记录点击时的页面来源\r\n };\r\n\r\n // 发送点击事件数据到服务端\r\n sendBehaviorData(behaviorData, reportUrl);\r\n }\r\n });\r\n};\r\n\r\n\r\n\r\n/**\r\n * @description: 捕获页面停留时间(关闭/隐藏)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackPageDwellTime = (projectName: string, reportUrl: string) => {\r\n // 在 beforeunload 时计算停留时间\r\n window.addEventListener('beforeunload', () => {\r\n reportDwellTime(projectName, reportUrl);\r\n });\r\n\r\n window.addEventListener('pagehide', () => {\r\n reportDwellTime(projectName, reportUrl);\r\n });\r\n\r\n // 在 visibilitychange(切换标签)时计算停留时间\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'hidden') {\r\n reportDwellTime(projectName, reportUrl);\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * @description: 捕获 SPA 路由行为(路由变化:先上报旧页面停留,再上报新页面 PV)\r\n * @param projectName 项目名称\r\n * @param reportUrl 上报 URL\r\n * @returns \r\n */\r\nconst trackSpaBehavior = (projectName: string, reportUrl: string) => {\r\n /**\r\n * @description: 处理路由变化事件回调\r\n * @returns \r\n */\r\n const handleRouteChange = () => {\r\n // 1. 防抖校验:如果 URL 没变(比如 hashchange 和 popstate 同时触发),直接退出\r\n if (window.location.href === lastPageUrl) return;\r\n\r\n // 2. 结算上一页:上报前一个页面的停留时间\r\n reportDwellTime(projectName, reportUrl);\r\n\r\n // 3. 记录当前 URL 为 referrer (在更新 lastPageUrl 之前!)\r\n const referrer = lastPageUrl;\r\n\r\n // 4. 更新状态:保存当前 URL,为下一次跳转做准备\r\n pageLoadTime = Date.now();\r\n lastPageUrl = window.location.href;\r\n\r\n // 5. 记录新页面:上报 PV\r\n const userId = getUserID();\r\n const pv = incrementPV();\r\n sendBehaviorData({\r\n behavior: 'pv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n pageUrl: window.location.href,\r\n referrer: referrer, // 这里的 referrer 是跳转前的页面 URL\r\n pv,\r\n }, reportUrl);\r\n };\r\n\r\n window.addEventListener('hashchange', handleRouteChange);\r\n window.addEventListener('popstate', handleRouteChange);\r\n\r\n const originalPush = history.pushState;\r\n const originalReplace = history.replaceState;\r\n\r\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\r\n originalPush.apply(this, args);\r\n handleRouteChange();\r\n };\r\n\r\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\r\n originalReplace.apply(this, args);\r\n handleRouteChange();\r\n };\r\n};","\r\nimport { trackUserBehavior } from './tracker';\r\nimport { getUserID, isUVRecorded, setUVRecorded } from './storage';\r\nimport { sendBehaviorData } from './sender';\r\n\r\ninterface MonitorConfig {\r\n projectName: string; // 当前项目名称,用于区分项目\r\n reportUrl: string; // 上报服务器的地址\r\n}\r\n\r\n// 初始化用户行为监控\r\nexport const initUserBehaviorMonitor = ({ projectName, reportUrl }: MonitorConfig) => {\r\n const userId = getUserID();\r\n\r\n // UV 统计:如果是用户首次访问,记录 UV\r\n if (!isUVRecorded()) {\r\n sendBehaviorData({\r\n behavior: 'uv',\r\n userId,\r\n projectName,\r\n timestamp: new Date().toISOString(),\r\n }, reportUrl);\r\n setUVRecorded();\r\n }\r\n\r\n // 启动点击行为、PV 统计和页面停留时间监控\r\n trackUserBehavior(projectName, reportUrl);\r\n};"],"names":["sendBehaviorData","data","url","navigator","sendBeacon","blob","Blob","JSON","stringify","type","fetch","method","headers","body","catch","error","console","USER_ID_KEY","PV_COUNT_KEY","UV_STORAGE_KEY","getUserID","userId","localStorage","getItem","generateUUID","setItem","replace","char","random","Math","toString","incrementPV","today","Date","toISOString","split","pvData","concat","newPV","parseInt","pageLoadTime","now","lastPageUrl","window","location","href","lastDwellReportedForLoadTime","reportDwellTime","projectName","reportUrl","dwellTime","behavior","timestamp","pageUrl","trackMpaPageView","addEventListener","pv","referrer","document","trackClicks","event","target","dataset","trackClick","behaviorData","element","tagName","action","trackPageDwellTime","visibilityState","trackSpaBehavior","handleRouteChange","originalPush","history","pushState","originalReplace","replaceState","args","_i","arguments","length","apply","this","_a","setUVRecorded","trackUserBehavior"],"mappings":"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"}
|
package/dist/sender.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "behavior-aliu",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight front-end behavior monitoring SDK",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"browser": "dist/index.umd.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "rollup -c",
|
|
11
|
+
"dev": "rollup -c -w",
|
|
12
|
+
"demo":"node test/server.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"behavior-monitor",
|
|
16
|
+
"frontend",
|
|
17
|
+
"sdk"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@rollup/plugin-terser": "^0.4.0",
|
|
25
|
+
"@rollup/plugin-typescript": "^11.1.0",
|
|
26
|
+
"cors": "^2.8.5",
|
|
27
|
+
"express": "^5.2.1",
|
|
28
|
+
"rollup": "^4.9.0",
|
|
29
|
+
"tslib": "^2.6.0",
|
|
30
|
+
"typescript": "^5.3.0"
|
|
31
|
+
}
|
|
32
|
+
}
|