@rk911/tracker-sdk 1.6.5 → 1.6.8

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/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) {
@@ -305,7 +308,7 @@ export function createPageStayMixin(tracker) {
305
308
 
306
309
  var config = tracker.getConfig()
307
310
  // 最小停留阈值:iOS 前后台抖动会产生大量 1~2s 的碎停留,默认 2000ms 过滤噪音
308
- var minDuration = (config.pageStay || {}).minStayDuration || 2000
311
+ var minDuration = (config.pageStay || {}).minStayDuration || 500
309
312
  if (duration < minDuration) return
310
313
 
311
314
  var maxDuration = (config.pageStay || {}).maxStayDuration || 1800000
@@ -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/core/config.js CHANGED
@@ -26,7 +26,7 @@ const DEFAULT_CONFIG = {
26
26
  pageStay: {
27
27
  heartbeatInterval: 5000,
28
28
  maxStayDuration: 1800000,
29
- minStayDuration: 2000,
29
+ minStayDuration: 500,
30
30
  },
31
31
 
32
32
  // 曝光插件配置
@@ -44,6 +44,11 @@ const DEFAULT_CONFIG = {
44
44
  milestone: false, // 是否只在 25/50/75/100% 里程碑时上报
45
45
  },
46
46
 
47
+ // API 监听插件配置
48
+ apiWatch: {
49
+ maxResponseDataLength: 1024, // API 响应体最大截断长度,0 表示不截断全量上报
50
+ },
51
+
47
52
  // 采样率 0-1,1 表示全量采集,0.5 表示 50% 的会话被采集
48
53
  // 采用会话级别采样,同一个 session 内所有事件要么全采要么全不采
49
54
  sampling: 1,
package/core/tracker.js CHANGED
@@ -224,6 +224,22 @@ export class Tracker {
224
224
  this._referrer = this._currentPage
225
225
  }
226
226
  this._currentPage = newPage
227
+ if (this._pageRouteUpdateListeners) {
228
+ this._pageRouteUpdateListeners.forEach(function (fn) {
229
+ try {
230
+ fn(newPage)
231
+ } catch (e) {}
232
+ })
233
+ }
234
+ }
235
+ }
236
+
237
+ onPageRouteUpdate(fn) {
238
+ if (!this._pageRouteUpdateListeners) {
239
+ this._pageRouteUpdateListeners = []
240
+ }
241
+ if (typeof fn === 'function') {
242
+ this._pageRouteUpdateListeners.push(fn)
227
243
  }
228
244
  }
229
245
 
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@rk911/tracker-sdk",
3
- "version": "1.6.5",
3
+ "version": "1.6.8",
4
4
  "description": "人康多端埋点 SDK — 自动化采集 + 手动埋点兼容",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
7
+ "type": "module",
7
8
  "files": [
8
9
  "index.js",
9
10
  "core/",
@@ -181,44 +181,26 @@ export class ApiWatchPlugin {
181
181
  return '[含base64内联数据,已省略 ' + str.length + ' 字节]'
182
182
  }
183
183
 
184
- var MAX = 1024
185
- if (str.length > MAX) {
186
- return str.substring(0, MAX) + '...[已截断,共' + str.length + '字节]'
184
+ var config = this.tracker ? this.tracker.getConfig() : {}
185
+ var apiWatchConfig = (config && config.apiWatch) || {}
186
+ var maxLen = apiWatchConfig.maxResponseDataLength !== undefined ? apiWatchConfig.maxResponseDataLength : 1024
187
+
188
+ if (maxLen > 0 && str.length > maxLen) {
189
+ return str.substring(0, maxLen) + '...[已截断,共' + str.length + '字节]'
187
190
  }
188
191
  return str
189
192
  }
190
193
 
194
+ // header 直接透传(不再做混淆/截断)——原 _encryptValue 会把 token 截断成前16字节
195
+ // 导致后台无法还原([解码失败]),已移除。敏感 header 现以明文上报。
191
196
  _filterHeaders(headers) {
192
197
  if (!headers || typeof headers !== 'object') return null
193
- var filtered = {}
194
- var sensitiveKeys = ['authorization', 'token', 'rsb-token', 'cookie', 'set-cookie']
198
+ var copy = {}
195
199
  for (var key in headers) {
196
200
  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 ''
201
+ copy[key] = headers[key]
221
202
  }
203
+ return copy
222
204
  }
223
205
 
224
206
  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
@@ -151,8 +152,8 @@ export class JsErrorPlugin {
151
152
  _handleRejection(event) {
152
153
  if (!this.tracker || !event) return
153
154
 
154
- var reason = event.reason || event
155
- var message = reason ? (reason.message || String(reason)) : 'Unknown rejection'
155
+ var reason = event.reason !== undefined ? event.reason : event
156
+ var message = this._formatErrorMessage(reason)
156
157
  var stack = (reason && reason.stack) ? reason.stack.substring(0, 1000) : ''
157
158
  var fingerprint = 'rejection|' + message
158
159
 
@@ -171,6 +172,20 @@ export class JsErrorPlugin {
171
172
  })
172
173
  }
173
174
 
175
+ _formatErrorMessage(err) {
176
+ if (!err) return 'Unknown error'
177
+ if (typeof err === 'string') return err
178
+ if (err.message && typeof err.message === 'string') return err.message
179
+ if (err.errMsg && typeof err.errMsg === 'string') return err.errMsg
180
+ if (err.msg && typeof err.msg === 'string') return err.msg
181
+ if (err.error && typeof err.error === 'string') return err.error
182
+ try {
183
+ var str = JSON.stringify(err)
184
+ if (str && str !== '{}') return str
185
+ } catch (e) {}
186
+ return String(err)
187
+ }
188
+
174
189
  _handleResourceError(event) {
175
190
  if (!this.tracker || !event || !event.target) return
176
191
 
@@ -15,6 +15,12 @@ export class PageViewPlugin {
15
15
  var self = this
16
16
 
17
17
  if (platform === 'web' || platform === 'app') {
18
+ // 联动:当页面 onLoad/onShow 主动 updatePageRoute 时,同步立刻触发 page_view,
19
+ // 保证页面返回/重定向等场景下 page_view 序列号与时间戳仍绝对领先于 api_request
20
+ tracker.onPageRouteUpdate(function (newPage) {
21
+ self._reportPageView()
22
+ })
23
+
18
24
  adapter.onRouteChange(function () {
19
25
  // 取消上一次未完成的检测(防止 pushState+hashchange 双触发)
20
26
  if (self._pendingTimer) {
@@ -90,6 +96,9 @@ export class PageViewPlugin {
90
96
  // 去重:同一个页面不重复上报
91
97
  if (currentPage === this._lastPage) return
92
98
 
99
+ // 锁定进页时刻的时间戳(避免被后方的 title 异步重试链延迟 100~500ms)
100
+ var entryTimestamp = adapter.getTimestamp()
101
+
93
102
  // 防抖:同路由 500ms 内不重复上报(防框架路由重入导致的竞态)
94
103
  var now = Date.now()
95
104
  if (this._lastFiredRoute === currentPage && now - this._lastFiredTime < 500) return
@@ -100,53 +109,48 @@ export class PageViewPlugin {
100
109
  this.tracker.updatePageRoute(currentPage)
101
110
 
102
111
  var self = this
103
- // 代次令牌:每次进入生成新代次,旧的 title 重试链检测到代次变化即中止
104
- // 防止导航交叠时,未完成的重试链把过期路由的 page_view 发出去
105
112
  var myGen = (this._pvGen = (this._pvGen || 0) + 1)
106
- var titleAttempts = 0
107
- var prevTitle = ''
108
- function reportWithTitle() {
109
- if (myGen !== self._pvGen) return
110
- var title = typeof document !== 'undefined' ? document.title : ''
111
- // 第一轮:无论 title 有无值,都等一个 tick 让框架有机会更新
112
- if (titleAttempts === 0) {
113
- prevTitle = title
114
- titleAttempts++
115
- setTimeout(reportWithTitle, 50)
116
- return
117
- }
118
- // title 为空:继续等
119
- if (!title && titleAttempts < 10) {
120
- titleAttempts++
121
- setTimeout(reportWithTitle, 50)
122
- return
123
- }
124
- // title 在两次检查间发生变化:说明框架刚更新了,再等一轮确认稳定
125
- if (title !== prevTitle && titleAttempts < 10) {
126
- prevTitle = title
127
- titleAttempts++
128
- setTimeout(reportWithTitle, 50)
129
- return
130
- }
131
- // 兜底:如果 title 在所有重试后仍未变化(框架未更新 document.title),
132
- // 尝试从 uni-app 页面栈读取 navigationBarTitle
133
- if (titleAttempts >= 10 && title === prevTitle) {
113
+
114
+ function getBestTitle() {
115
+ var t = typeof document !== 'undefined' ? document.title : ''
116
+ if (!t) {
134
117
  try {
135
118
  var pages = getCurrentPages()
136
- var cur = pages[pages.length - 1]
137
- var stackTitle = (cur && cur.$page && cur.$page.meta && cur.$page.meta.navigationBar
138
- ? cur.$page.meta.navigationBar.titleText : '') || ''
139
- if (stackTitle) title = stackTitle
119
+ if (pages && pages.length) {
120
+ var cur = pages[pages.length - 1]
121
+ t = (cur && cur.$page && cur.$page.meta && cur.$page.meta.navigationBar
122
+ ? cur.$page.meta.navigationBar.titleText : '') || ''
123
+ }
140
124
  } catch (e) {}
141
125
  }
142
- self._lastTitle = title
143
- self.tracker.track('page_view', {
144
- pageRoute: currentPage,
145
- page: currentPage,
146
- pageTitle: title,
147
- })
126
+ return t
127
+ }
128
+
129
+ // 立刻同步触发 page_view 打点,保证 page_view 序列号和时间戳严格领先于该页面后续发起的 api_request
130
+ var initialTitle = getBestTitle()
131
+ self._lastTitle = initialTitle
132
+ self.tracker.track('page_view', {
133
+ pageRoute: currentPage,
134
+ page: currentPage,
135
+ pageTitle: initialTitle,
136
+ timestamp: entryTimestamp,
137
+ })
138
+
139
+ // 若首帧标题尚空,开启轻量轮询更新本地标题记录
140
+ if (!initialTitle && typeof document !== 'undefined') {
141
+ var attempts = 0
142
+ function pollTitle() {
143
+ if (myGen !== self._pvGen) return
144
+ var t = getBestTitle()
145
+ if (t) {
146
+ self._lastTitle = t
147
+ } else if (attempts < 5) {
148
+ attempts++
149
+ setTimeout(pollTitle, 50)
150
+ }
151
+ }
152
+ setTimeout(pollTitle, 50)
148
153
  }
149
- reportWithTitle()
150
154
  }
151
155
 
152
156
  // 供 mixin 调用(小程序端)