@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 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 || 2000
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: 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/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
- this.deviceId = deviceId
66
- this.adapter.setStorage(DEVICE_ID_KEY, deviceId)
67
- this._save()
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 existingId = adapter.getStorage('deviceId')
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
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@rk911/tracker-sdk",
3
- "version": "1.6.7",
3
+ "version": "1.6.9",
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,9 +181,12 @@ 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
  }
@@ -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 || event
156
- var message = reason ? (reason.message || String(reason)) : 'Unknown rejection'
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
 
@@ -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
- var titleAttempts = 0
110
- var prevTitle = ''
111
- function reportWithTitle() {
112
- if (myGen !== self._pvGen) return
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
- var cur = pages[pages.length - 1]
140
- var stackTitle = (cur && cur.$page && cur.$page.meta && cur.$page.meta.navigationBar
141
- ? cur.$page.meta.navigationBar.titleText : '') || ''
142
- 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
+ }
143
124
  } catch (e) {}
144
125
  }
145
- self._lastTitle = title
146
- self.tracker.track('page_view', {
147
- pageRoute: currentPage,
148
- page: currentPage,
149
- pageTitle: title,
150
- timestamp: entryTimestamp,
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 调用(小程序端)