@rk911/tracker-sdk 1.7.9 → 1.7.10
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 +50 -14
- package/core/session.js +20 -6
- package/core/tracker.js +30 -1
- package/index.js +98 -35
- package/native/android/AndroidTracker.java +154 -3
- package/package.json +1 -1
- package/platforms/web.js +66 -10
- package/plugins/page-view.js +2 -2
package/core/compat.js
CHANGED
|
@@ -155,6 +155,8 @@ export function createPageStayMixin(tracker) {
|
|
|
155
155
|
data: function () {
|
|
156
156
|
return {
|
|
157
157
|
_pageStayStartTime: 0,
|
|
158
|
+
_accumulatedDuration: 0,
|
|
159
|
+
_isStayPaused: false,
|
|
158
160
|
_pageStayRoute: '',
|
|
159
161
|
_pageStayTitle: '',
|
|
160
162
|
}
|
|
@@ -188,6 +190,8 @@ export function createPageStayMixin(tracker) {
|
|
|
188
190
|
}
|
|
189
191
|
} catch (e) {}
|
|
190
192
|
this._pageStayStartTime = Date.now()
|
|
193
|
+
this._accumulatedDuration = 0
|
|
194
|
+
this._isStayPaused = false
|
|
191
195
|
this._pageShowReported = false
|
|
192
196
|
},
|
|
193
197
|
|
|
@@ -201,15 +205,35 @@ export function createPageStayMixin(tracker) {
|
|
|
201
205
|
}
|
|
202
206
|
window.addEventListener('pageshow', this._pageshowHandler)
|
|
203
207
|
|
|
208
|
+
// 监听 tracker 的挂起与恢复事件(如 App 切后台、多 Tab/多 WebView 切换)
|
|
209
|
+
this._stayPauseHandler = function () {
|
|
210
|
+
if (!self._pageStayRoute) return
|
|
211
|
+
if (self._pageStayStartTime > 0) {
|
|
212
|
+
self._accumulatedDuration += (Date.now() - self._pageStayStartTime)
|
|
213
|
+
self._pageStayStartTime = 0
|
|
214
|
+
}
|
|
215
|
+
self._isStayPaused = true
|
|
216
|
+
}
|
|
217
|
+
this._stayResumeHandler = function () {
|
|
218
|
+
if (!self._pageStayRoute) return
|
|
219
|
+
if (self._isTopStayPage()) {
|
|
220
|
+
self._pageStayStartTime = Date.now()
|
|
221
|
+
self._isStayPaused = false
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
tracker.onPauseStay(this._stayPauseHandler)
|
|
225
|
+
tracker.onResumeStay(this._stayResumeHandler)
|
|
226
|
+
|
|
204
227
|
// H5 下 app 前后台切换(visibilitychange)不总触发页面 onShow/onHide,
|
|
205
|
-
//
|
|
206
|
-
// 用 visibilitychange 兜底(app-lifecycle 已证明它可靠触发):
|
|
228
|
+
// 用 visibilitychange 兜底:
|
|
207
229
|
// 仅栈顶页面响应,与 onShow/onHide 幂等共存(重复触发因 start=0 早退,不会重报)。
|
|
208
230
|
if (typeof document !== 'undefined') {
|
|
209
231
|
this._pageVisHandler = function () {
|
|
210
232
|
if (!self._isTopStayPage()) return
|
|
211
233
|
if (document.visibilityState === 'visible') {
|
|
212
|
-
self.
|
|
234
|
+
if (!self._isStayPaused) {
|
|
235
|
+
self._pageStayStartTime = Date.now()
|
|
236
|
+
}
|
|
213
237
|
} else if (document.visibilityState === 'hidden') {
|
|
214
238
|
self._pageStayReport()
|
|
215
239
|
}
|
|
@@ -221,6 +245,7 @@ export function createPageStayMixin(tracker) {
|
|
|
221
245
|
onShow: function () {
|
|
222
246
|
if (!this._pageStayRoute) return
|
|
223
247
|
this._pageStayStartTime = Date.now()
|
|
248
|
+
this._isStayPaused = false
|
|
224
249
|
if (!this._pageShowReported) {
|
|
225
250
|
this._pageShowReported = true
|
|
226
251
|
this._pageShowReport('onShow')
|
|
@@ -257,15 +282,20 @@ export function createPageStayMixin(tracker) {
|
|
|
257
282
|
// 仅栈顶页响应,避免后台页面误报停留。
|
|
258
283
|
_isTopStayPage: function () {
|
|
259
284
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
285
|
+
if (typeof getCurrentPages === 'function') {
|
|
286
|
+
var pages = getCurrentPages()
|
|
287
|
+
if (pages && pages.length) {
|
|
288
|
+
var top = pages[pages.length - 1]
|
|
289
|
+
var route = top && top.route
|
|
290
|
+
? (top.route.charAt(0) === '/' ? top.route : '/' + top.route)
|
|
291
|
+
: ''
|
|
292
|
+
return route === this._pageStayRoute
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
var currentPath = tracker.getAdapter().getCurrentPagePath()
|
|
296
|
+
return currentPath === this._pageStayRoute
|
|
267
297
|
} catch (e) {
|
|
268
|
-
return
|
|
298
|
+
return true
|
|
269
299
|
}
|
|
270
300
|
},
|
|
271
301
|
|
|
@@ -302,12 +332,18 @@ export function createPageStayMixin(tracker) {
|
|
|
302
332
|
},
|
|
303
333
|
|
|
304
334
|
_pageStayReport: function () {
|
|
305
|
-
|
|
306
|
-
|
|
335
|
+
var duration = this._accumulatedDuration
|
|
336
|
+
if (this._pageStayStartTime > 0) {
|
|
337
|
+
duration += (Date.now() - this._pageStayStartTime)
|
|
338
|
+
}
|
|
307
339
|
this._pageStayStartTime = 0
|
|
340
|
+
this._accumulatedDuration = 0
|
|
341
|
+
this._isStayPaused = false
|
|
342
|
+
|
|
343
|
+
if (duration <= 0) return
|
|
308
344
|
|
|
309
345
|
var config = tracker.getConfig()
|
|
310
|
-
// 最小停留阈值:iOS
|
|
346
|
+
// 最小停留阈值:iOS/切后台抖动会产生大量 1~2s 的碎停留,默认 500ms 过滤噪音
|
|
311
347
|
var minDuration = (config.pageStay || {}).minStayDuration || 500
|
|
312
348
|
if (duration < minDuration) return
|
|
313
349
|
|
package/core/session.js
CHANGED
|
@@ -61,13 +61,20 @@ export class SessionManager {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
setUser(userId, isNative = false) {
|
|
64
|
-
this.userId
|
|
64
|
+
if (!userId && this.userId && !isNative) {
|
|
65
|
+
// 防误置空:外部未准备好用户身份传空时,若内存中已有合法 userId,绝不置空;清空需调用 resetSession
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
this.userId = userId || null
|
|
65
69
|
this.isNativeUserId = !!isNative
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
if (userId) {
|
|
71
|
+
this.adapter.setStorage('_tracker_user_id', userId)
|
|
72
|
+
if (!isNative) {
|
|
73
|
+
this.adapter.setStorage('userId', userId)
|
|
74
|
+
}
|
|
69
75
|
} else if (!userId && !isNative) {
|
|
70
76
|
this.adapter.removeStorage('userId')
|
|
77
|
+
this.adapter.removeStorage('_tracker_user_id')
|
|
71
78
|
}
|
|
72
79
|
this._save()
|
|
73
80
|
}
|
|
@@ -92,7 +99,7 @@ export class SessionManager {
|
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
setInitialSequence(seq) {
|
|
95
|
-
if (typeof seq === 'number' && !isNaN(seq) && seq
|
|
102
|
+
if (typeof seq === 'number' && !isNaN(seq) && seq >= this.sequence) {
|
|
96
103
|
this.sequence = seq
|
|
97
104
|
this._save()
|
|
98
105
|
}
|
|
@@ -152,13 +159,20 @@ export class SessionManager {
|
|
|
152
159
|
this._createNew()
|
|
153
160
|
}
|
|
154
161
|
if (!this.userId) {
|
|
155
|
-
this.userId = this.adapter.getStorage('userId')
|
|
162
|
+
this.userId = this.adapter.getStorage('userId')
|
|
163
|
+
|| this.adapter.getStorage('unionid')
|
|
164
|
+
|| this.adapter.getStorage('_tracker_user_id')
|
|
165
|
+
|| null
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
resetSession() {
|
|
160
170
|
this.adapter.removeSessionStorage(this._storageKey)
|
|
171
|
+
this.adapter.removeStorage('_tracker_user_id')
|
|
161
172
|
this.userId = null
|
|
173
|
+
this.isNativeUserId = false
|
|
174
|
+
this.isNativeSessionId = false
|
|
175
|
+
this.isNativeDeviceId = false
|
|
162
176
|
this._createNew()
|
|
163
177
|
}
|
|
164
178
|
|
package/core/tracker.js
CHANGED
|
@@ -150,6 +150,7 @@ export class Tracker {
|
|
|
150
150
|
eventName: eventData.eventName || eventType,
|
|
151
151
|
timestamp: this.adapter.getTimestamp(),
|
|
152
152
|
sequence: seq,
|
|
153
|
+
userId: 'userId' in eventData ? eventData.userId : (this.session.getUserId() || null),
|
|
153
154
|
pageRoute: 'pageRoute' in eventData ? (eventData.pageRoute || null) : (this._currentPage || this.adapter.getCurrentPagePath()),
|
|
154
155
|
pageTitle: 'pageTitle' in eventData ? (eventData.pageTitle || null) : (this.adapter.getPageTitle ? this.adapter.getPageTitle() : null),
|
|
155
156
|
elementId: eventData.elementId || null,
|
|
@@ -162,7 +163,7 @@ export class Tracker {
|
|
|
162
163
|
extKey: eventData.extKey || null,
|
|
163
164
|
extValue: eventData.extValue || null,
|
|
164
165
|
extIntValue: eventData.extIntValue || null,
|
|
165
|
-
frontUrl: eventData.frontUrl || null,
|
|
166
|
+
frontUrl: eventData.frontUrl || (typeof window !== 'undefined' && window.location ? window.location.href : null),
|
|
166
167
|
postParam: eventData.postParam != null && eventData.postParam !== ''
|
|
167
168
|
? (typeof eventData.postParam === 'string'
|
|
168
169
|
? eventData.postParam
|
|
@@ -293,6 +294,34 @@ export class Tracker {
|
|
|
293
294
|
return this._referrer
|
|
294
295
|
}
|
|
295
296
|
|
|
297
|
+
// ---- 页面停留挂起与恢复控制 ----
|
|
298
|
+
|
|
299
|
+
onPauseStay(fn) {
|
|
300
|
+
if (!this._stayPauseListeners) this._stayPauseListeners = []
|
|
301
|
+
if (typeof fn === 'function') this._stayPauseListeners.push(fn)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
onResumeStay(fn) {
|
|
305
|
+
if (!this._stayResumeListeners) this._stayResumeListeners = []
|
|
306
|
+
if (typeof fn === 'function') this._stayResumeListeners.push(fn)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
pausePageStay() {
|
|
310
|
+
if (this._stayPauseListeners) {
|
|
311
|
+
this._stayPauseListeners.forEach(function (fn) {
|
|
312
|
+
try { fn() } catch (e) {}
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
resumePageStay() {
|
|
318
|
+
if (this._stayResumeListeners) {
|
|
319
|
+
this._stayResumeListeners.forEach(function (fn) {
|
|
320
|
+
try { fn() } catch (e) {}
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
296
325
|
// ---- 动态参数解析器 ----
|
|
297
326
|
|
|
298
327
|
registerParam(name, fn) {
|
package/index.js
CHANGED
|
@@ -199,9 +199,33 @@ function registerDeviceIfNeeded(tracker) {
|
|
|
199
199
|
attempt(0)
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
function _parseJwtPayload(token) {
|
|
203
|
+
if (!token || typeof token !== 'string' || token.indexOf('.') === -1) return null
|
|
204
|
+
try {
|
|
205
|
+
var tokenParts = token.split('.')
|
|
206
|
+
if (tokenParts.length >= 2) {
|
|
207
|
+
var base64Url = tokenParts[1]
|
|
208
|
+
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
|
209
|
+
var decodedStr = ''
|
|
210
|
+
if (typeof atob === 'function') {
|
|
211
|
+
try {
|
|
212
|
+
decodedStr = decodeURIComponent(escape(atob(base64)))
|
|
213
|
+
} catch (e1) {
|
|
214
|
+
decodedStr = atob(base64)
|
|
215
|
+
}
|
|
216
|
+
} else if (typeof Buffer !== 'undefined') {
|
|
217
|
+
decodedStr = Buffer.from(base64, 'base64').toString('utf-8')
|
|
218
|
+
}
|
|
219
|
+
if (decodedStr) {
|
|
220
|
+
return JSON.parse(decodedStr)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {}
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
202
227
|
/**
|
|
203
|
-
* 自动检测 userId
|
|
204
|
-
* 不依赖内存缓存,确保清除存储后不会残留旧用户、切换账号后能及时更新
|
|
228
|
+
* 自动检测 userId:多源交叉读取,并确保不把已有的合法身份置空
|
|
205
229
|
*/
|
|
206
230
|
function _ensureUserId(tracker) {
|
|
207
231
|
var session = tracker.getSession()
|
|
@@ -211,10 +235,47 @@ function _ensureUserId(tracker) {
|
|
|
211
235
|
}
|
|
212
236
|
|
|
213
237
|
var adapter = tracker.getAdapter()
|
|
214
|
-
|
|
215
|
-
|
|
238
|
+
var uid = null
|
|
239
|
+
|
|
240
|
+
// 1. 跨端 NativeBridge 同步上下文优先(如果运行在 Android/iOS WebView 中)
|
|
241
|
+
if (typeof window !== 'undefined' && window.NativeBridge && typeof window.NativeBridge.getNativeContextSync === 'function') {
|
|
242
|
+
try {
|
|
243
|
+
var nativeRaw = window.NativeBridge.getNativeContextSync()
|
|
244
|
+
if (nativeRaw) {
|
|
245
|
+
var nativeCtx = typeof nativeRaw === 'string' ? JSON.parse(nativeRaw) : nativeRaw
|
|
246
|
+
if (nativeCtx && nativeCtx.userId) {
|
|
247
|
+
uid = String(nativeCtx.userId)
|
|
248
|
+
}
|
|
249
|
+
if (nativeCtx && nativeCtx.currentSequence) {
|
|
250
|
+
session.setInitialSequence(Number(nativeCtx.currentSequence))
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. URL query 跨端注入参数兜底
|
|
257
|
+
if (!uid && typeof window !== 'undefined' && window.location && window.location.search) {
|
|
258
|
+
try {
|
|
259
|
+
var search = window.location.search
|
|
260
|
+
var matchSeq = search.match(/[?&]_trk_seq=([^&]+)/)
|
|
261
|
+
if (matchSeq) {
|
|
262
|
+
session.setInitialSequence(Number(decodeURIComponent(matchSeq[1])))
|
|
263
|
+
}
|
|
264
|
+
var matchUid = search.match(/[?&]_trk_userId=([^&]+)/)
|
|
265
|
+
if (matchUid) {
|
|
266
|
+
uid = decodeURIComponent(matchUid[1])
|
|
267
|
+
}
|
|
268
|
+
} catch (e) {}
|
|
269
|
+
}
|
|
216
270
|
|
|
217
|
-
//
|
|
271
|
+
// 3. 存储单字段:userId / unionid / _tracker_user_id
|
|
272
|
+
if (!uid) {
|
|
273
|
+
uid = adapter.getStorage('userId')
|
|
274
|
+
|| adapter.getStorage('unionid')
|
|
275
|
+
|| adapter.getStorage('_tracker_user_id')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 4. 读取 userInfo 复合对象
|
|
218
279
|
if (!uid) {
|
|
219
280
|
try {
|
|
220
281
|
var rawInfo = adapter.getStorage('userInfo')
|
|
@@ -227,43 +288,43 @@ function _ensureUserId(tracker) {
|
|
|
227
288
|
} catch (e) {}
|
|
228
289
|
}
|
|
229
290
|
|
|
230
|
-
//
|
|
291
|
+
// 5. Cookie 兜底:rkfeb-userId 或 static-c-token (JWT payload)
|
|
231
292
|
if (!uid && typeof document !== 'undefined') {
|
|
232
|
-
var match = document.cookie.match(/(?:^|;\s*)rkfeb-userId=([^;]+)/)
|
|
233
|
-
if (match) uid = decodeURIComponent(match[1])
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 4. 关键兜底:从 token / appToken (JWT payload) 中解析 unionid 或 userId
|
|
237
|
-
if (!uid) {
|
|
238
293
|
try {
|
|
239
|
-
var
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
var
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
decodedStr = decodeURIComponent(escape(atob(base64)))
|
|
249
|
-
} catch (e1) {
|
|
250
|
-
decodedStr = atob(base64)
|
|
251
|
-
}
|
|
252
|
-
} else if (typeof Buffer !== 'undefined') {
|
|
253
|
-
decodedStr = Buffer.from(base64, 'base64').toString('utf-8')
|
|
254
|
-
}
|
|
255
|
-
if (decodedStr) {
|
|
256
|
-
var payload = JSON.parse(decodedStr)
|
|
257
|
-
uid = payload.unionid || payload.userId || payload.uid || payload.sub
|
|
294
|
+
var matchCookieUid = document.cookie.match(/(?:^|;\s*)rkfeb-userId=([^;]+)/)
|
|
295
|
+
if (matchCookieUid) uid = decodeURIComponent(matchCookieUid[1])
|
|
296
|
+
|
|
297
|
+
if (!uid) {
|
|
298
|
+
var matchCookieToken = document.cookie.match(/(?:^|;\s*)(?:token|static-c-token|appToken)=([^;]+)/)
|
|
299
|
+
if (matchCookieToken) {
|
|
300
|
+
var payloadCookie = _parseJwtPayload(decodeURIComponent(matchCookieToken[1]))
|
|
301
|
+
if (payloadCookie) {
|
|
302
|
+
uid = payloadCookie.unionid || payloadCookie.userId || payloadCookie.uid || payloadCookie.sub
|
|
258
303
|
}
|
|
259
304
|
}
|
|
260
305
|
}
|
|
261
306
|
} catch (e) {}
|
|
262
307
|
}
|
|
263
308
|
|
|
264
|
-
|
|
265
|
-
if (uid
|
|
266
|
-
|
|
309
|
+
// 6. JWT Token 存储兜底:token / Token / appToken / rsc-token / rsb-token
|
|
310
|
+
if (!uid) {
|
|
311
|
+
var tokenKeys = ['token', 'Token', 'appToken', 'rsc-token', 'rsb-token']
|
|
312
|
+
for (var i = 0; i < tokenKeys.length; i++) {
|
|
313
|
+
var tokenVal = adapter.getStorage(tokenKeys[i])
|
|
314
|
+
if (tokenVal) {
|
|
315
|
+
var payload = _parseJwtPayload(tokenVal)
|
|
316
|
+
if (payload) {
|
|
317
|
+
uid = payload.unionid || payload.userId || payload.uid || payload.sub
|
|
318
|
+
if (uid) break
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 7. 防误置空核心保护:只有获取到有效非空 uid 才更新
|
|
325
|
+
var currentUid = session.getUserId()
|
|
326
|
+
if (uid && String(uid) !== String(currentUid)) {
|
|
327
|
+
tracker.setUser(String(uid))
|
|
267
328
|
}
|
|
268
329
|
}
|
|
269
330
|
|
|
@@ -288,9 +349,11 @@ function install(Vue, options) {
|
|
|
288
349
|
|
|
289
350
|
var tracker = createTracker(options || {})
|
|
290
351
|
|
|
291
|
-
// 0. 暴露 tracker 实例到 window
|
|
352
|
+
// 0. 暴露 tracker 实例到 window(供原生容器及验证脚本访问)
|
|
292
353
|
if (typeof window !== 'undefined') {
|
|
293
354
|
window.__tracker = tracker
|
|
355
|
+
window.__tracker_pauseStay = function () { tracker.pausePageStay() }
|
|
356
|
+
window.__tracker_resumeStay = function () { tracker.resumePageStay() }
|
|
294
357
|
}
|
|
295
358
|
|
|
296
359
|
// 0.1 设备注册(无 deviceId 时自动注册)
|
|
@@ -45,6 +45,7 @@ import java.net.URL;
|
|
|
45
45
|
import java.util.ArrayList;
|
|
46
46
|
import java.util.HashMap;
|
|
47
47
|
import java.util.List;
|
|
48
|
+
import java.util.Iterator;
|
|
48
49
|
import java.util.Map;
|
|
49
50
|
|
|
50
51
|
/**
|
|
@@ -85,19 +86,32 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
85
86
|
private String referrerPageRoute = "";
|
|
86
87
|
private String lastClickKey = "";
|
|
87
88
|
private long lastClickTime = 0;
|
|
89
|
+
private String lastLegacyClickKey = "";
|
|
90
|
+
private long lastLegacyClickTime = 0;
|
|
88
91
|
|
|
89
92
|
private static final Map<String, String> NATIVE_TITLE_MAP = new HashMap<>();
|
|
90
93
|
static {
|
|
91
94
|
NATIVE_TITLE_MAP.put("com.renrui.main.activity.MainActivity", "APP主页");
|
|
92
95
|
NATIVE_TITLE_MAP.put("com.renrui.main.fragment.MainTabFragment", "首页Tab");
|
|
93
|
-
NATIVE_TITLE_MAP.put("com.renrui.main.fragment.MainRecommentFragment", "
|
|
96
|
+
NATIVE_TITLE_MAP.put("com.renrui.main.fragment.MainRecommentFragment", "推荐精选/短视频");
|
|
94
97
|
NATIVE_TITLE_MAP.put("com.renrui.main.fragment.MainWebFragment", "首页Web容器");
|
|
95
98
|
NATIVE_TITLE_MAP.put("com.renrui.mine.fragment.MineFragment", "个人中心/我的");
|
|
96
99
|
NATIVE_TITLE_MAP.put("com.renrui.mine.activity.MineWebActivity", "我的Web容器");
|
|
97
100
|
NATIVE_TITLE_MAP.put("com.renrui.mine.fragment.MineWebFragment", "我的Web页面");
|
|
101
|
+
NATIVE_TITLE_MAP.put("com.renrui.wz.activity.PageActivity", "APP主页");
|
|
98
102
|
NATIVE_TITLE_MAP.put("com.renrui.wz.activity.SplashActivity", "启动闪屏页");
|
|
103
|
+
NATIVE_TITLE_MAP.put("com.renrui.wz.activity.LoginActivity", "登录页");
|
|
104
|
+
NATIVE_TITLE_MAP.put("com.renrui.wz.activity.LoginPhoneActivity", "手机号验证码登录");
|
|
105
|
+
NATIVE_TITLE_MAP.put("com.dongsh.zxinglibrary.android.CaptureActivity", "扫码页");
|
|
99
106
|
NATIVE_TITLE_MAP.put("com.renrui.web.CommonWebActivity", "通用H5容器");
|
|
100
107
|
NATIVE_TITLE_MAP.put("com.renrui.web.CommonWebFragment", "通用H5页面");
|
|
108
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.fragment.HomeFragment", "首页短视频");
|
|
109
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.fragment.HomeReCommendFragment", "小视频推荐");
|
|
110
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.activity.TikTokActivity", "小视频首页");
|
|
111
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.activity.HomePlayletHistoryActivity", "短剧历史记录");
|
|
112
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.fragment.HomePlayletHistoryFragment", "短剧历史列表");
|
|
113
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.activity.HomeVideoPlayActivity", "短视频全屏播放页");
|
|
114
|
+
NATIVE_TITLE_MAP.put("com.renrui.home.activity.HomePlayletPlayActivity", "短剧播放页");
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
public static String getFriendlyPageTitle(String className) {
|
|
@@ -109,8 +123,11 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
109
123
|
if (lastDot >= 0 && lastDot < className.length() - 1) {
|
|
110
124
|
String simple = className.substring(lastDot + 1);
|
|
111
125
|
if (simple.contains("MainTab")) return "首页/主Tab";
|
|
112
|
-
if (simple.contains("Recomment")) return "
|
|
126
|
+
if (simple.contains("Recomment") || simple.contains("ReCommend") || simple.contains("Recommend")) return "小视频推荐";
|
|
113
127
|
if (simple.contains("Mine")) return "个人中心";
|
|
128
|
+
if (simple.contains("PlayletHistory")) return "短剧历史记录";
|
|
129
|
+
if (simple.contains("PlayletPlay")) return "短剧播放页";
|
|
130
|
+
if (simple.contains("VideoPlay")) return "短视频全屏播放页";
|
|
114
131
|
if (simple.contains("Splash")) return "启动闪屏页";
|
|
115
132
|
if (simple.contains("Login")) return "登录页";
|
|
116
133
|
if (simple.contains("Web")) return "Web视图";
|
|
@@ -296,6 +313,58 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
296
313
|
/**
|
|
297
314
|
* 手动自定义埋点 (Custom Tracking)
|
|
298
315
|
*/
|
|
316
|
+
/**
|
|
317
|
+
* 视频全生命周期行为埋点 (短视频起播、滑走、互动)
|
|
318
|
+
* @param eventName 动作名: video_play_start, video_play_end, video_like, video_unlike, video_share, video_collect 等
|
|
319
|
+
* @param videoId 视频ID
|
|
320
|
+
* @param videoTitle 视频标题 (直接赋予 elementText,保证看板直观可读)
|
|
321
|
+
* @param customProperties 扩展参数 (分类、作者、播放时长等)
|
|
322
|
+
*/
|
|
323
|
+
public void trackVideo(final String eventName, final long videoId, final String videoTitle, final JSONObject customProperties) {
|
|
324
|
+
postBgTask(new Runnable() {
|
|
325
|
+
@Override
|
|
326
|
+
public void run() {
|
|
327
|
+
try {
|
|
328
|
+
JSONObject event = createBaseEvent("video", eventName);
|
|
329
|
+
event.put("elementId", "video_" + videoId);
|
|
330
|
+
if (!TextUtils.isEmpty(videoTitle)) {
|
|
331
|
+
event.put("elementText", videoTitle);
|
|
332
|
+
} else {
|
|
333
|
+
event.put("elementText", "video_" + videoId);
|
|
334
|
+
}
|
|
335
|
+
event.put("pageTitle", "小视频推荐");
|
|
336
|
+
|
|
337
|
+
JSONObject extra = new JSONObject();
|
|
338
|
+
String baseExtraStr = event.optString("extraData");
|
|
339
|
+
if (!TextUtils.isEmpty(baseExtraStr)) {
|
|
340
|
+
try {
|
|
341
|
+
extra = new JSONObject(baseExtraStr);
|
|
342
|
+
} catch (Exception ignored) {}
|
|
343
|
+
}
|
|
344
|
+
extra.put("videoId", videoId);
|
|
345
|
+
if (!TextUtils.isEmpty(videoTitle)) {
|
|
346
|
+
extra.put("videoTitle", videoTitle);
|
|
347
|
+
}
|
|
348
|
+
if (customProperties != null) {
|
|
349
|
+
Iterator<String> keys = customProperties.keys();
|
|
350
|
+
while (keys.hasNext()) {
|
|
351
|
+
String k = keys.next();
|
|
352
|
+
extra.put(k, customProperties.opt(k));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (customProperties != null && customProperties.has("playDurationMs")) {
|
|
356
|
+
event.put("duration", customProperties.optLong("playDurationMs"));
|
|
357
|
+
}
|
|
358
|
+
event.put("extraData", extra.toString());
|
|
359
|
+
enqueueEvent(event);
|
|
360
|
+
flushAsync();
|
|
361
|
+
} catch (Exception e) {
|
|
362
|
+
Log.e(TAG, "===> [Native trackVideo Failed]", e);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
299
368
|
public void track(final String eventName, final JSONObject customProperties) {
|
|
300
369
|
postBgTask(new Runnable() {
|
|
301
370
|
@Override
|
|
@@ -318,6 +387,14 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
318
387
|
* 旧 $dadian 点位兼容方法 (自动匹配后端双写 point.log)
|
|
319
388
|
*/
|
|
320
389
|
public void trackFrontUrl(final String frontUrl, final String elementId, final String postParam) {
|
|
390
|
+
final String key = (frontUrl != null ? frontUrl : "") + "|" + (elementId != null ? elementId : "");
|
|
391
|
+
final long now = System.currentTimeMillis();
|
|
392
|
+
if (key.equals(lastLegacyClickKey) && (now - lastLegacyClickTime < 500)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
lastLegacyClickKey = key;
|
|
396
|
+
lastLegacyClickTime = now;
|
|
397
|
+
|
|
321
398
|
postBgTask(new Runnable() {
|
|
322
399
|
@Override
|
|
323
400
|
public void run() {
|
|
@@ -409,6 +486,32 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
409
486
|
}
|
|
410
487
|
}
|
|
411
488
|
|
|
489
|
+
public String getNetworkType() {
|
|
490
|
+
try {
|
|
491
|
+
if (application == null) return "";
|
|
492
|
+
android.net.ConnectivityManager cm = (android.net.ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
493
|
+
if (cm == null) return "";
|
|
494
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
|
495
|
+
android.net.Network activeNetwork = cm.getActiveNetwork();
|
|
496
|
+
if (activeNetwork == null) return "";
|
|
497
|
+
android.net.NetworkCapabilities caps = cm.getNetworkCapabilities(activeNetwork);
|
|
498
|
+
if (caps == null) return "";
|
|
499
|
+
if (caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_WIFI)) return "wifi";
|
|
500
|
+
if (caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_CELLULAR)) return "4g";
|
|
501
|
+
if (caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_ETHERNET)) return "ethernet";
|
|
502
|
+
return "other";
|
|
503
|
+
} else {
|
|
504
|
+
android.net.NetworkInfo info = cm.getActiveNetworkInfo();
|
|
505
|
+
if (info == null || !info.isConnected()) return "";
|
|
506
|
+
if (info.getType() == android.net.ConnectivityManager.TYPE_WIFI) return "wifi";
|
|
507
|
+
if (info.getType() == android.net.ConnectivityManager.TYPE_MOBILE) return "4g";
|
|
508
|
+
return "other";
|
|
509
|
+
}
|
|
510
|
+
} catch (Throwable e) {
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
412
515
|
private JSONObject createBaseEvent(String eventType, String eventName) throws Exception {
|
|
413
516
|
sequence++;
|
|
414
517
|
JSONObject event = new JSONObject();
|
|
@@ -418,6 +521,10 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
418
521
|
event.put("timestamp", System.currentTimeMillis());
|
|
419
522
|
event.put("sequence", sequence);
|
|
420
523
|
event.put("pageRoute", currentPageRoute);
|
|
524
|
+
String autoTitle = getFriendlyPageTitle(currentPageRoute);
|
|
525
|
+
if (!TextUtils.isEmpty(autoTitle)) {
|
|
526
|
+
event.put("pageTitle", autoTitle);
|
|
527
|
+
}
|
|
421
528
|
if (!TextUtils.isEmpty(referrerPageRoute)) {
|
|
422
529
|
event.put("referrer", referrerPageRoute);
|
|
423
530
|
}
|
|
@@ -425,6 +532,10 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
425
532
|
event.put("deviceBrand", android.os.Build.BRAND != null ? android.os.Build.BRAND : "");
|
|
426
533
|
event.put("deviceModel", android.os.Build.MODEL != null ? android.os.Build.MODEL : "");
|
|
427
534
|
event.put("osVersion", android.os.Build.VERSION.RELEASE != null ? android.os.Build.VERSION.RELEASE : "");
|
|
535
|
+
String net = getNetworkType();
|
|
536
|
+
if (!TextUtils.isEmpty(net)) {
|
|
537
|
+
event.put("networkType", net);
|
|
538
|
+
}
|
|
428
539
|
|
|
429
540
|
JSONObject extra = new JSONObject();
|
|
430
541
|
if (!TextUtils.isEmpty(phone)) extra.put("phone", phone);
|
|
@@ -642,6 +753,10 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
642
753
|
@Override
|
|
643
754
|
public void onFragmentResumed(FragmentManager fm, Fragment f) {
|
|
644
755
|
super.onFragmentResumed(fm, f);
|
|
756
|
+
// 过滤嵌套子 Fragment(如 ViewPager 内的 ChildFragment),由主 Fragment 统一承担页面路由和时长计算,防止同屏惊群割裂卡片
|
|
757
|
+
if (f.getParentFragment() != null) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
645
760
|
final String fragmentRoute = f.getClass().getName();
|
|
646
761
|
postBgTask(new Runnable() {
|
|
647
762
|
@Override
|
|
@@ -665,6 +780,35 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
665
780
|
}
|
|
666
781
|
});
|
|
667
782
|
}
|
|
783
|
+
|
|
784
|
+
@Override
|
|
785
|
+
public void onFragmentPaused(FragmentManager fm, Fragment f) {
|
|
786
|
+
super.onFragmentPaused(fm, f);
|
|
787
|
+
if (f.getParentFragment() != null) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
final String fragmentRoute = f.getClass().getName();
|
|
791
|
+
postBgTask(new Runnable() {
|
|
792
|
+
@Override
|
|
793
|
+
public void run() {
|
|
794
|
+
try {
|
|
795
|
+
if (fragmentRoute.equals(currentPageRoute) && lastPageStartTime > 0) {
|
|
796
|
+
long duration = System.currentTimeMillis() - lastPageStartTime;
|
|
797
|
+
if (duration >= 500) {
|
|
798
|
+
JSONObject event = createBaseEvent("page_stay", "page_stay");
|
|
799
|
+
event.put("duration", duration);
|
|
800
|
+
event.put("pageTitle", getFriendlyPageTitle(fragmentRoute));
|
|
801
|
+
enqueueEvent(event);
|
|
802
|
+
flushAsync();
|
|
803
|
+
}
|
|
804
|
+
lastPageStartTime = 0;
|
|
805
|
+
}
|
|
806
|
+
} catch (Exception e) {
|
|
807
|
+
Log.e(TAG, "===> [Native Fragment Paused Error]", e);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
}
|
|
668
812
|
}, true);
|
|
669
813
|
}
|
|
670
814
|
|
|
@@ -846,7 +990,11 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
846
990
|
Log.i(TAG, "===> [Native Activity Resumed] Page: " + currentPageRoute + ", Referrer: " + referrerPageRoute);
|
|
847
991
|
JSONObject event = createBaseEvent("page_view", "page_view");
|
|
848
992
|
String activityTitle = activity.getTitle() != null ? activity.getTitle().toString() : "";
|
|
849
|
-
|
|
993
|
+
String friendly = getFriendlyPageTitle(route);
|
|
994
|
+
if (TextUtils.isEmpty(activityTitle) || "银友圈".equals(activityTitle)) {
|
|
995
|
+
activityTitle = friendly;
|
|
996
|
+
}
|
|
997
|
+
event.put("pageTitle", activityTitle);
|
|
850
998
|
enqueueEvent(event);
|
|
851
999
|
flushAsync();
|
|
852
1000
|
} catch (Exception e) {
|
|
@@ -867,6 +1015,9 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
867
1015
|
if (duration >= 500) {
|
|
868
1016
|
JSONObject event = createBaseEvent("page_stay", "page_stay");
|
|
869
1017
|
event.put("duration", duration);
|
|
1018
|
+
String actTitle = activity.getTitle() != null ? activity.getTitle().toString() : "";
|
|
1019
|
+
String friendly = getFriendlyPageTitle(activity.getClass().getName());
|
|
1020
|
+
event.put("pageTitle", !TextUtils.isEmpty(friendly) ? friendly : actTitle);
|
|
870
1021
|
enqueueEvent(event);
|
|
871
1022
|
flushAsync();
|
|
872
1023
|
}
|
package/package.json
CHANGED
package/platforms/web.js
CHANGED
|
@@ -44,21 +44,50 @@ export class WebAdapter {
|
|
|
44
44
|
|
|
45
45
|
getStorage(key) {
|
|
46
46
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
if (typeof uni !== 'undefined' && typeof uni.getStorageSync === 'function') {
|
|
48
|
+
const val = uni.getStorageSync(key)
|
|
49
|
+
if (val !== undefined && val !== null && val !== '') return val
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {}
|
|
52
|
+
try {
|
|
53
|
+
if (typeof localStorage !== 'undefined') {
|
|
54
|
+
const val = localStorage.getItem(key)
|
|
55
|
+
if (val !== null && val !== undefined && val !== '') {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(val)
|
|
58
|
+
} catch (e1) {
|
|
59
|
+
return val
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
return null
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
setStorage(key, value) {
|
|
54
68
|
try {
|
|
55
|
-
uni.setStorageSync
|
|
69
|
+
if (typeof uni !== 'undefined' && typeof uni.setStorageSync === 'function') {
|
|
70
|
+
uni.setStorageSync(key, value)
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {}
|
|
73
|
+
try {
|
|
74
|
+
if (typeof localStorage !== 'undefined') {
|
|
75
|
+
const strVal = typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value)
|
|
76
|
+
localStorage.setItem(key, strVal)
|
|
77
|
+
}
|
|
56
78
|
} catch (e) {}
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
removeStorage(key) {
|
|
60
82
|
try {
|
|
61
|
-
uni.removeStorageSync
|
|
83
|
+
if (typeof uni !== 'undefined' && typeof uni.removeStorageSync === 'function') {
|
|
84
|
+
uni.removeStorageSync(key)
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {}
|
|
87
|
+
try {
|
|
88
|
+
if (typeof localStorage !== 'undefined') {
|
|
89
|
+
localStorage.removeItem(key)
|
|
90
|
+
}
|
|
62
91
|
} catch (e) {}
|
|
63
92
|
}
|
|
64
93
|
|
|
@@ -193,7 +222,21 @@ export class WebAdapter {
|
|
|
193
222
|
}
|
|
194
223
|
|
|
195
224
|
getToken() {
|
|
196
|
-
|
|
225
|
+
var key = this.config.tokenKey || 'token'
|
|
226
|
+
var t = this.getStorage(key)
|
|
227
|
+
if (t) return t
|
|
228
|
+
var candidates = ['token', 'Token', 'appToken', 'rsc-token', 'rsb-token', 'Authorization']
|
|
229
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
230
|
+
var candidate = candidates[i]
|
|
231
|
+
if (candidate === key) continue
|
|
232
|
+
t = this.getStorage(candidate)
|
|
233
|
+
if (t) return t
|
|
234
|
+
}
|
|
235
|
+
if (typeof document !== 'undefined') {
|
|
236
|
+
var match = document.cookie.match(/(?:^|;\s*)(?:token|static-c-token|appToken)=([^;]+)/)
|
|
237
|
+
if (match) return decodeURIComponent(match[1])
|
|
238
|
+
}
|
|
239
|
+
return null
|
|
197
240
|
}
|
|
198
241
|
|
|
199
242
|
// [PROJECT-ADAPT] 网络请求实现
|
|
@@ -234,9 +277,17 @@ export class WebAdapter {
|
|
|
234
277
|
// ---- 网络状态 ----
|
|
235
278
|
|
|
236
279
|
getNetworkType() {
|
|
280
|
+
try {
|
|
281
|
+
if (typeof window !== 'undefined' && window.NativeBridge && typeof window.NativeBridge.getNetworkType === 'function') {
|
|
282
|
+
const net = window.NativeBridge.getNetworkType()
|
|
283
|
+
if (net) return net
|
|
284
|
+
}
|
|
285
|
+
} catch (e) {}
|
|
237
286
|
try {
|
|
238
287
|
if (typeof navigator !== 'undefined' && navigator.connection) {
|
|
239
|
-
|
|
288
|
+
if (navigator.connection.type && navigator.connection.type !== 'unknown') {
|
|
289
|
+
return navigator.connection.type
|
|
290
|
+
}
|
|
240
291
|
}
|
|
241
292
|
} catch (e) {}
|
|
242
293
|
return typeof navigator !== 'undefined' && navigator.onLine ? 'wifi' : 'offline'
|
|
@@ -246,15 +297,20 @@ export class WebAdapter {
|
|
|
246
297
|
|
|
247
298
|
getDeviceInfo() {
|
|
248
299
|
try {
|
|
249
|
-
const info = uni.getSystemInfoSync
|
|
300
|
+
const info = (typeof uni !== 'undefined' && typeof uni.getSystemInfoSync === 'function')
|
|
301
|
+
? (uni.getSystemInfoSync() || {})
|
|
302
|
+
: {}
|
|
250
303
|
var ua = (typeof navigator !== 'undefined' ? navigator.userAgent : '').toLowerCase()
|
|
251
304
|
var rawBrand = info.brand || this._judgeBrand(ua, info.model) || ''
|
|
252
305
|
var brand = rawBrand.toUpperCase() === 'OPPO' ? 'OPPO' : rawBrand
|
|
306
|
+
var rawSystem = info.system || ''
|
|
307
|
+
// 去除重复的 "Android " 前缀,与 Native 端对齐为纯版本号(如 "16")
|
|
308
|
+
var cleanSystem = rawSystem.replace(/^Android\s*/i, '').trim() || rawSystem
|
|
253
309
|
return {
|
|
254
310
|
brand: brand,
|
|
255
311
|
model: info.model || '',
|
|
256
312
|
platform: (info.platform || '').toLowerCase() === 'android' ? 'Android' : (info.platform || ''),
|
|
257
|
-
system:
|
|
313
|
+
system: cleanSystem,
|
|
258
314
|
screenWidth: info.screenWidth || 0,
|
|
259
315
|
screenHeight: info.screenHeight || 0,
|
|
260
316
|
userAgent:
|
package/plugins/page-view.js
CHANGED
|
@@ -99,9 +99,9 @@ export class PageViewPlugin {
|
|
|
99
99
|
// 锁定进页时刻的时间戳(避免被后方的 title 异步重试链延迟 100~500ms)
|
|
100
100
|
var entryTimestamp = adapter.getTimestamp()
|
|
101
101
|
|
|
102
|
-
// 防抖:同路由
|
|
102
|
+
// 防抖:同路由 800ms 内不重复上报(防框架路由重入或组件双重挂载导致的竞态)
|
|
103
103
|
var now = Date.now()
|
|
104
|
-
if (this._lastFiredRoute === currentPage && now - this._lastFiredTime <
|
|
104
|
+
if (this._lastFiredRoute === currentPage && now - this._lastFiredTime < 800) return
|
|
105
105
|
|
|
106
106
|
this._lastPage = currentPage
|
|
107
107
|
this._lastFiredRoute = currentPage
|