@rk911/tracker-sdk 1.6.4 → 1.6.7

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 ADDED
@@ -0,0 +1,204 @@
1
+ # @rk911/tracker-sdk
2
+
3
+ 人康多端埋点 SDK — 自动化采集 + 手动埋点兼容。
4
+
5
+ 支持 H5、微信小程序、APP(uni-app Vue 2)三端统一的前端数据采集方案,提供自动采集(页面浏览、点击、页面停留、元素曝光、JS 错误、API 监控、滚动深度、应用生命周期、性能指标)和手动埋点(`$dadian` / `$track`)能力。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install @rk911/tracker-sdk
11
+ ```
12
+
13
+ 或直接将 `tracker-sdk/` 文件夹复制到项目根目录。
14
+
15
+ ## 快速开始
16
+
17
+ ### Vue 插件方式(推荐)
18
+
19
+ ```javascript
20
+ import TrackerSDK from '@rk911/tracker-sdk'
21
+
22
+ Vue.use(TrackerSDK, {
23
+ appId: '1',
24
+ dadianUrl: 'https://state.renruikeji.cn',
25
+ platform: 'web', // 'web' | 'weapp' | 'app'
26
+ pflag: 0,
27
+ tokenKey: 'token',
28
+ plugins: ['page-view', 'click', 'page-stay', 'exposure', 'js-error', 'api-watch', 'app-lifecycle', 'performance', 'scroll-depth'],
29
+ getAuthHeaders: () => ({ Authorization: token, token: token }),
30
+ useUnifiedReport: true,
31
+ debug: false,
32
+ })
33
+ ```
34
+
35
+ ### 非 Vue 上下文
36
+
37
+ ```javascript
38
+ import { getTracker } from '@rk911/tracker-sdk'
39
+
40
+ const tracker = getTracker()
41
+ if (tracker) {
42
+ tracker.track('custom', { eventName: 'someEvent', data: 'value' })
43
+ }
44
+ ```
45
+
46
+ ## 配置项
47
+
48
+ | 参数 | 类型 | 默认值 | 说明 |
49
+ |------|------|--------|------|
50
+ | `appId` | string | — | **必填**,应用 ID |
51
+ | `dadianUrl` | string | — | **必填**,上报地址 |
52
+ | `platform` | string | `'web'` | 平台:`web` / `weapp` / `app` |
53
+ | `pflag` | number | `0` | 业务平台标识(0-5) |
54
+ | `tokenKey` | string | `'token'` | 登录 token 的 storage key(小程序用 `'tokenV2'`) |
55
+ | `plugins` | array | `[]` | 启用的插件列表 |
56
+ | `getAuthHeaders` | function | — | 返回请求头的函数 |
57
+ | `useUnifiedReport` | boolean | `true` | `true`=仅 SDK 上报,`false`=双通道上报 |
58
+ | `allowAnonymousTrack` | boolean | `false` | 是否允许未登录上报 |
59
+ | `sampling` | number | `1` | 采样率(0-1),会话级生效 |
60
+ | `debug` | boolean | `false` | 开启调试日志 |
61
+ | `queue.maxBatchSize` | number | `10` | 批量上报大小 |
62
+ | `queue.flushInterval` | number | `60000` | 定时上报间隔(ms) |
63
+ | `queue.clickFlushDelay` | number | `3000` | 点击事件延迟上报(ms) |
64
+ | `session.timeout` | number | `1800000` | 会话超时时间(ms) |
65
+ | `pageStay.minStayDuration` | number | `2000` | 最小停留时长(ms) |
66
+ | `pageStay.maxStayDuration` | number | `1800000` | 最大停留时长(ms) |
67
+ | `exposure.validExposureTime` | number | `1000` | 曝光有效时间(ms) |
68
+ | `exposure.threshold` | number | `0.5` | 曝光面积阈值 |
69
+ | `scrollDepth.throttle` | number | `1000` | 滚动节流间隔(ms) |
70
+
71
+ ## API
72
+
73
+ ### Vue 实例方法
74
+
75
+ | 方法 | 说明 |
76
+ |------|------|
77
+ | `this.$track(frontUrl, postParam)` | 手动埋点(推荐) |
78
+ | `this.$dadian(frontUrl, postParam)` | 手动埋点(向后兼容) |
79
+ | `this.$tracker` | 获取 Tracker 实例 |
80
+ | `this.$trackerResetSession()` | 重置会话(登出时调用) |
81
+ | `this.$registerParam(name, fn)` | 注册动态参数解析器 |
82
+
83
+ ### Vue 指令
84
+
85
+ | 指令 | 说明 |
86
+ |------|------|
87
+ | `v-track-exposure` | 元素曝光指令(推荐) |
88
+ | `v-impression` | 曝光兼容指令(旧项目) |
89
+
90
+ ### Tracker 实例方法
91
+
92
+ | 方法 | 说明 |
93
+ |------|------|
94
+ | `tracker.track(eventType, data)` | 核心事件上报 |
95
+ | `tracker.setUser(userId)` | 设置用户 ID |
96
+ | `tracker.setDeviceId(deviceId)` | 设置设备 ID |
97
+ | `tracker.setUserProperties(props)` | 设置用户属性 |
98
+ | `tracker.registerParam(name, fn)` | 注册动态参数解析器 |
99
+ | `tracker.unregisterParam(name)` | 注销动态参数解析器 |
100
+ | `tracker.updatePageRoute(newPage)` | 手动更新页面路由 |
101
+ | `tracker.getReferrer()` | 获取当前 referrer |
102
+ | `tracker.onReport(fn)` | 监听事件入队 |
103
+ | `tracker.destroy()` | 销毁实例,释放资源 |
104
+
105
+ ## 数据属性
106
+
107
+ 在 HTML 元素上添加以下 `data-track-*` 属性来丰富点击事件数据:
108
+
109
+ ```html
110
+ <button
111
+ data-track-id="order_submit"
112
+ data-track-name="提交订单"
113
+ data-track-ext-key="orderId"
114
+ data-track-ext-value="123456"
115
+ data-track-param="orderDetail"
116
+ >
117
+ 提交
118
+ </button>
119
+ ```
120
+
121
+ | 属性 | 说明 |
122
+ |------|------|
123
+ | `data-track-id` | 埋点标识(会将事件类型提升为 `custom`) |
124
+ | `data-track-name` | 人类可读名称 |
125
+ | `data-track-ext-key` | 扩展字段 key |
126
+ | `data-track-ext-value` | 扩展字段 value |
127
+ | `data-track-ext-int` | 扩展整数字段 |
128
+ | `data-track-param` | 动态参数解析器名称 |
129
+ | `data-track-post-*` | 自动收集到 postParam 对象 |
130
+
131
+ ## 动态参数解析器
132
+
133
+ ```javascript
134
+ this.$registerParam('goodsDetail', (el) => ({
135
+ extKey: 'goodsId',
136
+ extValue: String(store.state.goods.id),
137
+ extIntValue: store.state.goods.price,
138
+ postParam: { goodsId: store.state.goods.id, skuId: store.state.goods.skuId }
139
+ }))
140
+ ```
141
+
142
+ ## 插件列表
143
+
144
+ | 插件 | 说明 |
145
+ |------|------|
146
+ | `page-view` | 自动追踪页面浏览,路由变化检测 |
147
+ | `click` | 自动追踪点击,丰富元素识别策略 |
148
+ | `page-stay` | 页面停留时长追踪 |
149
+ | `exposure` | 元素曝光(IntersectionObserver + MutationObserver) |
150
+ | `js-error` | JS 错误捕获(运行时、Promise、资源加载、Vue 组件错误) |
151
+ | `api-watch` | API 请求监控(uni.addInterceptor) |
152
+ | `app-lifecycle` | 前后台切换事件 |
153
+ | `performance` | Web Vitals(DNS、TTFB、FCP、LCP、DOM Ready、Load) |
154
+ | `scroll-depth` | 滚动深度追踪 |
155
+
156
+ ## 平台支持
157
+
158
+ | 平台 | 适配器 | 说明 |
159
+ |------|--------|------|
160
+ | H5 | `WebAdapter` | 完整 DOM 访问,支持 hash/history 路由 |
161
+ | 微信小程序 | `WeappAdapter` | 无 DOM,内存 sessionStorage,生命周期 mixin |
162
+ | APP | `AppAdapter` | 继承 WebAdapter,集成 NativeBridge |
163
+
164
+ 平台自动检测:有 `wx` 且无 `document` → 小程序;有 `window` + `document` → web;默认 web。
165
+
166
+ ## 项目结构
167
+
168
+ ```
169
+ tracker-sdk/
170
+ ├── index.js # 入口:Vue 插件、createTracker、平台检测
171
+ ├── core/
172
+ │ ├── tracker.js # Tracker 类:插件管理、事件上报、采样
173
+ │ ├── config.js # 默认配置、深度合并、校验
174
+ │ ├── session.js # 会话管理:ID、设备、过期、序列号
175
+ │ ├── queue.js # 上报队列:批量、定时、离线缓存、sendBeacon
176
+ │ └── compat.js # 兼容层:$dadian、v-impression、page-stay mixin
177
+ ├── platforms/
178
+ │ ├── web.js # H5 适配器
179
+ │ ├── weapp.js # 微信小程序适配器
180
+ │ └── app.js # APP 适配器(NativeBridge)
181
+ ├── plugins/
182
+ │ ├── page-view.js # 页面浏览
183
+ │ ├── click.js # 点击
184
+ │ ├── page-stay.js # 页面停留
185
+ │ ├── exposure.js # 元素曝光
186
+ │ ├── js-error.js # JS 错误
187
+ │ ├── api-watch.js # API 监控
188
+ │ ├── app-lifecycle.js # 应用生命周期
189
+ │ ├── performance.js # 性能指标
190
+ │ └── scroll-depth.js # 滚动深度
191
+ ├── customize/
192
+ │ ├── index.js # 项目规则分发
193
+ │ └── modules/
194
+ │ ├── common.js # 公共 uni-app 规则
195
+ │ └── store-manager.js # 店掌项目规则
196
+ └── docs/
197
+ ├── SDK_DESIGN.md # 设计文档
198
+ ├── BACKEND_DESIGN.md # 后端接口设计
199
+ └── ROADMAP.md # 路线图
200
+ ```
201
+
202
+ ## License
203
+
204
+ MIT
package/core/compat.js CHANGED
@@ -178,6 +178,9 @@ export function createPageStayMixin(tracker) {
178
178
  } catch (e) {}
179
179
  if (!route) return
180
180
  this._pageStayRoute = route.charAt(0) === '/' ? route : '/' + route
181
+ this._pageStayReferrer = tracker.getReferrer() || null
182
+ // 解决 onLoad 内打点 pageRoute 错位问题:进入新页面 onLoad 时立即同步更新 SDK 路由状态
183
+ tracker.updatePageRoute(this._pageStayRoute)
181
184
  this._pageStayTitle = ''
182
185
  try {
183
186
  if (typeof document !== 'undefined' && document.title) {
@@ -332,6 +335,7 @@ export function createPageStayMixin(tracker) {
332
335
 
333
336
  tracker.track('page_stay', {
334
337
  pageRoute: this._pageStayRoute,
338
+ referrer: this._pageStayReferrer || tracker.getReferrer() || null,
335
339
  pageTitle: this._pageStayTitle || null,
336
340
  duration: duration,
337
341
  frontUrl: stayFrontUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk911/tracker-sdk",
3
- "version": "1.6.4",
3
+ "version": "1.6.7",
4
4
  "description": "人康多端埋点 SDK — 自动化采集 + 手动埋点兼容",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -11,7 +11,12 @@
11
11
  "plugins/",
12
12
  "customize/"
13
13
  ],
14
- "keywords": ["tracker", "analytics", "uni-app", "埋点"],
14
+ "keywords": [
15
+ "tracker",
16
+ "analytics",
17
+ "uni-app",
18
+ "埋点"
19
+ ],
15
20
  "author": "rk-team",
16
21
  "license": "MIT",
17
22
  "publishConfig": {
@@ -188,37 +188,16 @@ export class ApiWatchPlugin {
188
188
  return str
189
189
  }
190
190
 
191
+ // header 直接透传(不再做混淆/截断)——原 _encryptValue 会把 token 截断成前16字节
192
+ // 导致后台无法还原([解码失败]),已移除。敏感 header 现以明文上报。
191
193
  _filterHeaders(headers) {
192
194
  if (!headers || typeof headers !== 'object') return null
193
- var filtered = {}
194
- var sensitiveKeys = ['authorization', 'token', 'rsb-token', 'cookie', 'set-cookie']
195
+ var copy = {}
195
196
  for (var key in headers) {
196
197
  if (!headers.hasOwnProperty(key)) continue
197
- if (sensitiveKeys.indexOf(key.toLowerCase()) >= 0) {
198
- filtered[key] = this._encryptValue(headers[key])
199
- } else {
200
- filtered[key] = headers[key]
201
- }
202
- }
203
- return filtered
204
- }
205
-
206
- _encryptValue(value) {
207
- if (!value) return ''
208
- try {
209
- var encoded = encodeURIComponent(value)
210
- var result = ''
211
- for (var i = 0; i < encoded.length; i++) {
212
- result += String.fromCharCode(encoded.charCodeAt(i) ^ 0x5A)
213
- }
214
- var full = btoa(result)
215
- if (full.length > 16) {
216
- return 'ENC:' + full.substring(0, 16) + '...'
217
- }
218
- return 'ENC:' + full
219
- } catch (e) {
220
- return ''
198
+ copy[key] = headers[key]
221
199
  }
200
+ return copy
222
201
  }
223
202
 
224
203
  destroy() {
package/plugins/click.js CHANGED
@@ -261,6 +261,19 @@ export class ClickPlugin {
261
261
  var text = (container.innerText || container.textContent || '').trim()
262
262
  // 排除空文本
263
263
  if (!text) return ''
264
+ // 若包含换行符(弹窗/引导层容器),提取最符合按钮特性的短行
265
+ if (text.indexOf('\n') > -1) {
266
+ var lines = text.split('\n').map(function (l) { return l.trim() }).filter(Boolean)
267
+ if (lines.length > 0) {
268
+ for (var i = lines.length - 1; i >= 0; i--) {
269
+ if (lines[i].length > 0 && lines[i].length <= 12) {
270
+ text = lines[i]
271
+ break
272
+ }
273
+ }
274
+ if (text.indexOf('\n') > -1) text = lines[0]
275
+ }
276
+ }
264
277
  // 排除图标字体单字符(Unicode 私用区)
265
278
  if (text.length === 1 && text.charCodeAt(0) > 0xe000) return ''
266
279
  // [PROJECT-ADAPT] 设计工具命名过滤
@@ -57,7 +57,8 @@ export class JsErrorPlugin {
57
57
  }
58
58
  }
59
59
 
60
- var fingerprint = 'vue|' + message + '|' + componentName + '|' + (info || '')
60
+ var route = this._getCurrentRoute()
61
+ var fingerprint = 'vue|' + route + '|' + message + '|' + componentName + '|' + (info || '')
61
62
 
62
63
  if (this._isDuplicate(fingerprint)) return
63
64
  if (!this._shouldReport()) return
@@ -90,6 +90,9 @@ export class PageViewPlugin {
90
90
  // 去重:同一个页面不重复上报
91
91
  if (currentPage === this._lastPage) return
92
92
 
93
+ // 锁定进页时刻的时间戳(避免被后方的 title 异步重试链延迟 100~500ms)
94
+ var entryTimestamp = adapter.getTimestamp()
95
+
93
96
  // 防抖:同路由 500ms 内不重复上报(防框架路由重入导致的竞态)
94
97
  var now = Date.now()
95
98
  if (this._lastFiredRoute === currentPage && now - this._lastFiredTime < 500) return
@@ -144,6 +147,7 @@ export class PageViewPlugin {
144
147
  pageRoute: currentPage,
145
148
  page: currentPage,
146
149
  pageTitle: title,
150
+ timestamp: entryTimestamp,
147
151
  })
148
152
  }
149
153
  reportWithTitle()