@rk911/tracker-sdk 1.6.7 → 1.6.9
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 +1 -1
- package/core/config.js +6 -1
- package/core/session.js +30 -6
- package/core/tracker.js +32 -4
- package/index.js +23 -1
- package/package.json +2 -1
- package/plugins/api-watch.js +6 -3
- package/plugins/js-error.js +16 -2
- package/plugins/page-view.js +42 -42
package/core/compat.js
CHANGED
|
@@ -308,7 +308,7 @@ export function createPageStayMixin(tracker) {
|
|
|
308
308
|
|
|
309
309
|
var config = tracker.getConfig()
|
|
310
310
|
// 最小停留阈值:iOS 前后台抖动会产生大量 1~2s 的碎停留,默认 2000ms 过滤噪音
|
|
311
|
-
var minDuration = (config.pageStay || {}).minStayDuration ||
|
|
311
|
+
var minDuration = (config.pageStay || {}).minStayDuration || 500
|
|
312
312
|
if (duration < minDuration) return
|
|
313
313
|
|
|
314
314
|
var maxDuration = (config.pageStay || {}).maxStayDuration || 1800000
|
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/session.js
CHANGED
|
@@ -10,6 +10,9 @@ export class SessionManager {
|
|
|
10
10
|
this.sequence = 0
|
|
11
11
|
this.userId = null
|
|
12
12
|
this.deviceId = null
|
|
13
|
+
this.isNativeSessionId = false
|
|
14
|
+
this.isNativeDeviceId = false
|
|
15
|
+
this.isNativeUserId = false
|
|
13
16
|
this._storageKey = config.session.storageKey || '_tracker_session'
|
|
14
17
|
this._renewListeners = []
|
|
15
18
|
|
|
@@ -32,6 +35,7 @@ export class SessionManager {
|
|
|
32
35
|
getSessionId() {
|
|
33
36
|
if (!this.sessionId || this._isExpired()) {
|
|
34
37
|
var wasRenew = !!this.sessionId
|
|
38
|
+
this.isNativeSessionId = false
|
|
35
39
|
this._createNew()
|
|
36
40
|
if (wasRenew) this._notifyRenew()
|
|
37
41
|
}
|
|
@@ -56,15 +60,34 @@ export class SessionManager {
|
|
|
56
60
|
this._save()
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
setUser(userId) {
|
|
63
|
+
setUser(userId, isNative = false) {
|
|
60
64
|
this.userId = userId
|
|
65
|
+
this.isNativeUserId = !!isNative
|
|
66
|
+
if (userId && !isNative) {
|
|
67
|
+
this.adapter.setStorage('userId', userId)
|
|
68
|
+
}
|
|
61
69
|
this._save()
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
setDeviceId(deviceId) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
setDeviceId(deviceId, isNative = false) {
|
|
73
|
+
if (deviceId) {
|
|
74
|
+
this.deviceId = deviceId
|
|
75
|
+
this.isNativeDeviceId = !!isNative
|
|
76
|
+
if (!isNative) {
|
|
77
|
+
this.adapter.setStorage(DEVICE_ID_KEY, deviceId)
|
|
78
|
+
this.adapter.setStorage('deviceId', deviceId)
|
|
79
|
+
}
|
|
80
|
+
this._save()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setSessionId(sessionId, isNative = false) {
|
|
85
|
+
if (sessionId) {
|
|
86
|
+
this.sessionId = sessionId
|
|
87
|
+
this.isNativeSessionId = !!isNative
|
|
88
|
+
this.lastActivityTime = this.adapter.getTimestamp()
|
|
89
|
+
this._save()
|
|
90
|
+
}
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
getUserId() {
|
|
@@ -95,13 +118,14 @@ export class SessionManager {
|
|
|
95
118
|
startTime: this.startTime,
|
|
96
119
|
lastActivityTime: this.lastActivityTime,
|
|
97
120
|
sequence: this.sequence,
|
|
121
|
+
isNativeSessionId: !!this.isNativeSessionId,
|
|
98
122
|
}
|
|
99
123
|
this.adapter.setSessionStorage(this._storageKey, data)
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
_restore() {
|
|
103
127
|
var data = this.adapter.getSessionStorage(this._storageKey)
|
|
104
|
-
if (data && data.sessionId) {
|
|
128
|
+
if (data && data.sessionId && !data.isNativeSessionId) {
|
|
105
129
|
this.sessionId = data.sessionId
|
|
106
130
|
this.startTime = data.startTime
|
|
107
131
|
this.lastActivityTime = data.lastActivityTime
|
package/core/tracker.js
CHANGED
|
@@ -200,12 +200,24 @@ export class Tracker {
|
|
|
200
200
|
|
|
201
201
|
// ---- 用户标识 ----
|
|
202
202
|
|
|
203
|
-
setUser(userId) {
|
|
204
|
-
this.session.setUser(userId)
|
|
203
|
+
setUser(userId, isNative = false) {
|
|
204
|
+
this.session.setUser(userId, isNative)
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
setDeviceId(deviceId) {
|
|
208
|
-
this.session.setDeviceId(deviceId)
|
|
207
|
+
setDeviceId(deviceId, isNative = false) {
|
|
208
|
+
this.session.setDeviceId(deviceId, isNative)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
setSessionId(sessionId, isNative = false) {
|
|
212
|
+
this.session.setSessionId(sessionId, isNative)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getDeviceId() {
|
|
216
|
+
return this.session.getDeviceId()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getSessionId() {
|
|
220
|
+
return this.session.getSessionId()
|
|
209
221
|
}
|
|
210
222
|
|
|
211
223
|
setUserProperties(properties) {
|
|
@@ -224,6 +236,22 @@ export class Tracker {
|
|
|
224
236
|
this._referrer = this._currentPage
|
|
225
237
|
}
|
|
226
238
|
this._currentPage = newPage
|
|
239
|
+
if (this._pageRouteUpdateListeners) {
|
|
240
|
+
this._pageRouteUpdateListeners.forEach(function (fn) {
|
|
241
|
+
try {
|
|
242
|
+
fn(newPage)
|
|
243
|
+
} catch (e) {}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
onPageRouteUpdate(fn) {
|
|
250
|
+
if (!this._pageRouteUpdateListeners) {
|
|
251
|
+
this._pageRouteUpdateListeners = []
|
|
252
|
+
}
|
|
253
|
+
if (typeof fn === 'function') {
|
|
254
|
+
this._pageRouteUpdateListeners.push(fn)
|
|
227
255
|
}
|
|
228
256
|
}
|
|
229
257
|
|
package/index.js
CHANGED
|
@@ -89,6 +89,15 @@ export function createTracker(config) {
|
|
|
89
89
|
var adapter = createAdapter(platform, config)
|
|
90
90
|
trackerInstance = new Tracker(config, adapter)
|
|
91
91
|
|
|
92
|
+
// 触发 onBeforeInit 钩子(必须在插件注册与首屏事件触发前执行,以便同步/异步注入 Native 上下文)
|
|
93
|
+
if (typeof config.onBeforeInit === 'function') {
|
|
94
|
+
try {
|
|
95
|
+
config.onBeforeInit(trackerInstance)
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.warn('[TrackerSDK] onBeforeInit error:', e)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
// 注册插件
|
|
93
102
|
var pluginNames = config.plugins || []
|
|
94
103
|
pluginNames.forEach(function (pluginName) {
|
|
@@ -125,7 +134,14 @@ export function getTracker() {
|
|
|
125
134
|
function registerDeviceIfNeeded(tracker) {
|
|
126
135
|
var adapter = tracker.getAdapter()
|
|
127
136
|
var config = tracker.getConfig()
|
|
128
|
-
var
|
|
137
|
+
var session = tracker.getSession()
|
|
138
|
+
|
|
139
|
+
// 如果是由 App/Native 动态注入的临时 DeviceID,跳过 H5 本地注册逻辑
|
|
140
|
+
if (session.isNativeDeviceId && session.getDeviceId()) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var existingId = session.getDeviceId() || adapter.getStorage('deviceId') || adapter.getStorage('_tracker_device_id')
|
|
129
145
|
if (existingId) {
|
|
130
146
|
tracker.setDeviceId(existingId)
|
|
131
147
|
return
|
|
@@ -188,6 +204,12 @@ function registerDeviceIfNeeded(tracker) {
|
|
|
188
204
|
* 不依赖内存缓存,确保清除存储后不会残留旧用户、切换账号后能及时更新
|
|
189
205
|
*/
|
|
190
206
|
function _ensureUserId(tracker) {
|
|
207
|
+
var session = tracker.getSession()
|
|
208
|
+
// 如果当前 userId 是由 Native 拼接注入的临时账号,不被 H5 本地 Cookie/Storage 覆写
|
|
209
|
+
if (session.isNativeUserId && session.getUserId()) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
191
213
|
var adapter = tracker.getAdapter()
|
|
192
214
|
// 从源头实时读取(登录页写入了 uni.setStorageSync('userId', ...))
|
|
193
215
|
var uid = adapter.getStorage('userId')
|
package/package.json
CHANGED
package/plugins/api-watch.js
CHANGED
|
@@ -181,9 +181,12 @@ 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
|
}
|
package/plugins/js-error.js
CHANGED
|
@@ -152,8 +152,8 @@ export class JsErrorPlugin {
|
|
|
152
152
|
_handleRejection(event) {
|
|
153
153
|
if (!this.tracker || !event) return
|
|
154
154
|
|
|
155
|
-
var reason = event.reason
|
|
156
|
-
var message =
|
|
155
|
+
var reason = event.reason !== undefined ? event.reason : event
|
|
156
|
+
var message = this._formatErrorMessage(reason)
|
|
157
157
|
var stack = (reason && reason.stack) ? reason.stack.substring(0, 1000) : ''
|
|
158
158
|
var fingerprint = 'rejection|' + message
|
|
159
159
|
|
|
@@ -172,6 +172,20 @@ export class JsErrorPlugin {
|
|
|
172
172
|
})
|
|
173
173
|
}
|
|
174
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
|
+
|
|
175
189
|
_handleResourceError(event) {
|
|
176
190
|
if (!this.tracker || !event || !event.target) return
|
|
177
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) {
|
|
@@ -103,54 +109,48 @@ export class PageViewPlugin {
|
|
|
103
109
|
this.tracker.updatePageRoute(currentPage)
|
|
104
110
|
|
|
105
111
|
var self = this
|
|
106
|
-
// 代次令牌:每次进入生成新代次,旧的 title 重试链检测到代次变化即中止
|
|
107
|
-
// 防止导航交叠时,未完成的重试链把过期路由的 page_view 发出去
|
|
108
112
|
var myGen = (this._pvGen = (this._pvGen || 0) + 1)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
var title = typeof document !== 'undefined' ? document.title : ''
|
|
114
|
-
// 第一轮:无论 title 有无值,都等一个 tick 让框架有机会更新
|
|
115
|
-
if (titleAttempts === 0) {
|
|
116
|
-
prevTitle = title
|
|
117
|
-
titleAttempts++
|
|
118
|
-
setTimeout(reportWithTitle, 50)
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
// title 为空:继续等
|
|
122
|
-
if (!title && titleAttempts < 10) {
|
|
123
|
-
titleAttempts++
|
|
124
|
-
setTimeout(reportWithTitle, 50)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
// title 在两次检查间发生变化:说明框架刚更新了,再等一轮确认稳定
|
|
128
|
-
if (title !== prevTitle && titleAttempts < 10) {
|
|
129
|
-
prevTitle = title
|
|
130
|
-
titleAttempts++
|
|
131
|
-
setTimeout(reportWithTitle, 50)
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
// 兜底:如果 title 在所有重试后仍未变化(框架未更新 document.title),
|
|
135
|
-
// 尝试从 uni-app 页面栈读取 navigationBarTitle
|
|
136
|
-
if (titleAttempts >= 10 && title === prevTitle) {
|
|
113
|
+
|
|
114
|
+
function getBestTitle() {
|
|
115
|
+
var t = typeof document !== 'undefined' ? document.title : ''
|
|
116
|
+
if (!t) {
|
|
137
117
|
try {
|
|
138
118
|
var pages = getCurrentPages()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
124
|
} catch (e) {}
|
|
144
125
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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)
|
|
152
153
|
}
|
|
153
|
-
reportWithTitle()
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// 供 mixin 调用(小程序端)
|