@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 +5 -1
- package/core/config.js +6 -1
- package/core/tracker.js +16 -0
- package/package.json +2 -1
- package/plugins/api-watch.js +11 -29
- package/plugins/click.js +13 -0
- package/plugins/js-error.js +18 -3
- package/plugins/page-view.js +45 -41
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 ||
|
|
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:
|
|
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
package/plugins/api-watch.js
CHANGED
|
@@ -181,44 +181,26 @@ export class ApiWatchPlugin {
|
|
|
181
181
|
return '[含base64内联数据,已省略 ' + str.length + ' 字节]'
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
var
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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
|
-
|
|
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] 设计工具命名过滤
|
package/plugins/js-error.js
CHANGED
|
@@ -57,7 +57,8 @@ export class JsErrorPlugin {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
var
|
|
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
|
|
155
|
-
var message =
|
|
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
|
|
package/plugins/page-view.js
CHANGED
|
@@ -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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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 调用(小程序端)
|