@rk911/tracker-sdk 1.7.3 → 1.7.5

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/session.js CHANGED
@@ -63,8 +63,11 @@ export class SessionManager {
63
63
  setUser(userId, isNative = false) {
64
64
  this.userId = userId
65
65
  this.isNativeUserId = !!isNative
66
+ // 纯 H5 登录才写入永久 localStorage;Native 注入仅保存在会话级 sessionStorage,防止 Native 退出登录后 H5 残留旧账号
66
67
  if (userId && !isNative) {
67
68
  this.adapter.setStorage('userId', userId)
69
+ } else if (!userId && !isNative) {
70
+ this.adapter.removeStorage('userId')
68
71
  }
69
72
  this._save()
70
73
  }
@@ -73,10 +76,8 @@ export class SessionManager {
73
76
  if (deviceId) {
74
77
  this.deviceId = deviceId
75
78
  this.isNativeDeviceId = !!isNative
76
- if (!isNative) {
77
- this.adapter.setStorage(DEVICE_ID_KEY, deviceId)
78
- this.adapter.setStorage('deviceId', deviceId)
79
- }
79
+ this.adapter.setStorage(DEVICE_ID_KEY, deviceId)
80
+ this.adapter.setStorage('deviceId', deviceId)
80
81
  this._save()
81
82
  }
82
83
  }
@@ -90,6 +91,13 @@ export class SessionManager {
90
91
  }
91
92
  }
92
93
 
94
+ setInitialSequence(seq) {
95
+ if (typeof seq === 'number' && !isNaN(seq) && seq > this.sequence) {
96
+ this.sequence = seq
97
+ this._save()
98
+ }
99
+ }
100
+
93
101
  getUserId() {
94
102
  return this.userId
95
103
  }
@@ -119,24 +127,33 @@ export class SessionManager {
119
127
  lastActivityTime: this.lastActivityTime,
120
128
  sequence: this.sequence,
121
129
  isNativeSessionId: !!this.isNativeSessionId,
130
+ userId: this.userId,
131
+ isNativeUserId: !!this.isNativeUserId,
122
132
  }
123
133
  this.adapter.setSessionStorage(this._storageKey, data)
124
134
  }
125
135
 
126
136
  _restore() {
127
137
  var data = this.adapter.getSessionStorage(this._storageKey)
128
- if (data && data.sessionId && !data.isNativeSessionId) {
138
+ if (data && data.sessionId) {
129
139
  this.sessionId = data.sessionId
130
140
  this.startTime = data.startTime
131
141
  this.lastActivityTime = data.lastActivityTime
132
142
  this.sequence = data.sequence || 0
133
-
143
+ this.isNativeSessionId = !!data.isNativeSessionId
144
+ this.isNativeUserId = !!data.isNativeUserId
145
+ if (data.userId) {
146
+ this.userId = data.userId
147
+ }
134
148
  if (this._isExpired()) {
135
149
  this._createNew()
136
150
  }
137
151
  } else {
138
152
  this._createNew()
139
153
  }
154
+ if (!this.userId) {
155
+ this.userId = this.adapter.getStorage('userId') || null
156
+ }
140
157
  }
141
158
 
142
159
  resetSession() {
package/core/tracker.js CHANGED
@@ -40,16 +40,25 @@ export class Tracker {
40
40
 
41
41
  _getDeviceFields() {
42
42
  var info = this.adapter.getDeviceInfo() || {}
43
+ if (this._overrideDeviceInfo) {
44
+ info = Object.assign({}, info, this._overrideDeviceInfo)
45
+ }
43
46
  return {
44
- deviceBrand: info.brand || null,
45
- deviceModel: info.model || null,
47
+ deviceBrand: info.brand || info.deviceBrand || null,
48
+ deviceModel: info.model || info.deviceModel || null,
46
49
  os: info.platform || info.os || null,
47
- osVersion: info.system || null,
50
+ osVersion: info.system || info.osVersion || null,
48
51
  screenWidth: info.screenWidth || null,
49
52
  screenHeight: info.screenHeight || null,
50
53
  }
51
54
  }
52
55
 
56
+ setDeviceInfo(info) {
57
+ if (info && typeof info === 'object') {
58
+ this._overrideDeviceInfo = Object.assign(this._overrideDeviceInfo || {}, info)
59
+ }
60
+ }
61
+
53
62
  // ---- 插件管理 ----
54
63
 
55
64
  use(plugin) {
@@ -231,6 +240,12 @@ export class Tracker {
231
240
  this.session.setSessionId(sessionId, isNative)
232
241
  }
233
242
 
243
+ setInitialSequence(seq) {
244
+ if (this.session && typeof this.session.setInitialSequence === 'function') {
245
+ this.session.setInitialSequence(seq)
246
+ }
247
+ }
248
+
234
249
  getDeviceId() {
235
250
  return this.session.getDeviceId()
236
251
  }
package/index.js CHANGED
@@ -221,8 +221,8 @@ function _ensureUserId(tracker) {
221
221
  var currentUid = tracker.getSession().getUserId()
222
222
  if (uid && uid !== currentUid) {
223
223
  tracker.setUser(uid)
224
- } else if (!uid && currentUid) {
225
- // 源头已清空(退出登录/清除缓存),同步清除内存中的旧值
224
+ } else if (!uid && currentUid && !session.isNativeUserId) {
225
+ // 仅针对纯 H5 登录场景:若源头已清空(退出登录/清除缓存),同步清除内存中的旧值
226
226
  tracker.setUser(null)
227
227
  }
228
228
  }
@@ -73,7 +73,7 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
73
73
  private volatile String userId;
74
74
  private volatile String phone;
75
75
  private volatile String storeName;
76
- private int sequence = 0;
76
+ private volatile int sequence = 0;
77
77
 
78
78
  private HandlerThread bgThread;
79
79
  private Handler bgHandler;
@@ -232,6 +232,10 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
232
232
  updateUser(userId, null, null);
233
233
  }
234
234
 
235
+ public synchronized int getCurrentSequence() {
236
+ return sequence;
237
+ }
238
+
235
239
  /**
236
240
  * H5 路由状态同步 (由 CommonWebPresenter 通过 NativeBridge 触发)
237
241
  */
@@ -254,6 +258,7 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
254
258
  }
255
259
 
256
260
  public void resetSession() {
261
+ this.sequence = 0;
257
262
  this.sessionId = "android_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 10000);
258
263
  Log.i(TAG, "===> [Native Tracker Session Reset] New SessionId: " + this.sessionId);
259
264
  }
@@ -745,13 +750,24 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
745
750
  return "";
746
751
  }
747
752
 
748
- private void handleNativeViewClick(final Activity activity, final View target, final float rawX, final float rawY) {
749
- String extracted = extractViewText(target);
750
- if (TextUtils.isEmpty(extracted) && target.getParent() instanceof View) {
751
- extracted = extractViewText((View) target.getParent());
752
- }
753
- final String elementText = extracted;
753
+ private String getPresetTextForElementId(String elementId) {
754
+ if (TextUtils.isEmpty(elementId)) return "";
755
+ String lower = elementId.toLowerCase();
756
+ if (lower.contains("to_top") || lower.contains("scroll_top")) return "置顶";
757
+ if (!lower.contains("background") && !lower.contains("feedback") && !lower.contains("fallback") && !lower.contains("callback")
758
+ && (lower.startsWith("back") || lower.endsWith("back") || lower.contains("_back") || lower.contains("back_") || lower.equals("back"))) return "返回";
759
+ if (lower.contains("close") || lower.contains("cloess") || lower.contains("dismiss")) return "关闭";
760
+ if (lower.contains("search")) return "搜索";
761
+ if (lower.contains("refresh")) return "刷新";
762
+ if (lower.contains("share")) return "分享";
763
+ if (lower.contains("more")) return "更多";
764
+ if (lower.contains("award")) return "领奖/关注";
765
+ if ((lower.contains("_phone") || lower.contains("phone_") || lower.startsWith("phone") || lower.endsWith("phone") || lower.contains("mobile"))
766
+ && !lower.contains("headphone") && !lower.contains("microphone")) return "手机号";
767
+ return "";
768
+ }
754
769
 
770
+ private void handleNativeViewClick(final Activity activity, final View target, final float rawX, final float rawY) {
755
771
  final String elementId;
756
772
  if (target.getId() != View.NO_ID) {
757
773
  String resName = "";
@@ -763,6 +779,22 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
763
779
  elementId = target.getClass().getSimpleName();
764
780
  }
765
781
 
782
+ String extracted = extractViewText(target);
783
+ if (TextUtils.isEmpty(extracted)) {
784
+ // 1. 优先使用知名图标 ID 映射(解决置顶、返回、关闭等图标抓取不到或被误抓的问题)
785
+ String preset = getPresetTextForElementId(elementId);
786
+ if (!TextUtils.isEmpty(preset)) {
787
+ extracted = preset;
788
+ } else if (target.getParent() instanceof ViewGroup) {
789
+ ViewGroup parentGroup = (ViewGroup) target.getParent();
790
+ // 2. 严格限制父容器:仅在直接小容器(子 View <= 4 且高度不超过 400px)内检索,禁止向整屏根容器穿透扩散
791
+ if (parentGroup.getChildCount() <= 4 && parentGroup.getHeight() < 400) {
792
+ extracted = extractViewText(parentGroup);
793
+ }
794
+ }
795
+ }
796
+ final String elementText = extracted;
797
+
766
798
  final String key = elementId + "|" + elementText;
767
799
  final long now = System.currentTimeMillis();
768
800
  if (key.equals(lastClickKey) && (now - lastClickTime < 800)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk911/tracker-sdk",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "人康多端埋点 SDK — 跨端自动化采集 + 手动埋点双写兼容",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
package/platforms/app.js CHANGED
@@ -25,9 +25,17 @@ export class AppAdapter extends WebAdapter {
25
25
  // 尝试从 JSBridge 获取更多原生设备信息
26
26
  try {
27
27
  if (typeof window !== 'undefined' && window.NativeBridge && window.NativeBridge.getDeviceInfo) {
28
- const nativeInfo = window.NativeBridge.getDeviceInfo()
29
- if (nativeInfo) {
30
- return { ...baseInfo, ...nativeInfo }
28
+ let nativeInfo = window.NativeBridge.getDeviceInfo()
29
+ if (typeof nativeInfo === 'string') {
30
+ try { nativeInfo = JSON.parse(nativeInfo) } catch (e) {}
31
+ }
32
+ if (nativeInfo && typeof nativeInfo === 'object') {
33
+ return {
34
+ ...baseInfo,
35
+ ...nativeInfo,
36
+ brand: nativeInfo.brand || nativeInfo.deviceBrand || baseInfo.brand,
37
+ model: nativeInfo.model || nativeInfo.deviceModel || baseInfo.model,
38
+ }
31
39
  }
32
40
  }
33
41
  } catch (e) {}
package/platforms/web.js CHANGED
@@ -1,3 +1,33 @@
1
+ export const ROUTE_TITLE_MAP = {
2
+ '/pages/circle/circleOfFriends/details': '圈子动态详情',
3
+ '/pages/circle/index': '银友圈',
4
+ '/pages/circle/circleOfFriends/sendFriends/sendFriends': '发布动态',
5
+ '/pages/circle/circleOfFriends/sendFriends/circleClass/circleClass': '圈子分类',
6
+ '/pages/circle/userHome/otherHome': '个人主页',
7
+ '/healthFu/index/index': '健康福/AI助手',
8
+ '/healthMonitor/pages/index': '健康监测/体检',
9
+ '/videoNote/index/index': '视频笔记',
10
+ '/pagesA/app/videoAnswerList': '答题精选',
11
+ '/pagesA/index/activity/activity': '活动中心',
12
+ '/pagesA/index/game/game': '游戏中心',
13
+ '/pagesA/member/videoRanking': '视频排行榜',
14
+ '/pages/VoicePunchIn/VoicePunchIn': '语音打卡',
15
+ '/taskCenter/index/index': '任务中心',
16
+ '/yangji/index/index': '福利活动',
17
+ '/pages/mall/goods/goodsList': '商品列表',
18
+ '/pages/mall/goods/goodsInfo': '商品详情',
19
+ '/pages/mall/goods/goodsDetail': '商品详情',
20
+ '/pages/mall/goods/order': '商品下单',
21
+ '/pages/mall/searchGoods/searchGoods': '商品搜索',
22
+ '/pages/mall/goods/shopAddressList': '收货地址',
23
+ '/pages/my/store/index': '我的小店',
24
+ '/pages/my/store/withdrawalList': '提现记录',
25
+ '/pages/integralMall/integralMall': '积分商城',
26
+ '/pages/integralMall/integralGoodsInfo': '积分商品详情',
27
+ '/pages/my/setting/setting': '设置',
28
+ '/pages/selfInvoice/index': '自助开票',
29
+ }
30
+
1
31
  export class WebAdapter {
2
32
  constructor(config) {
3
33
  this.config = config
@@ -149,12 +179,16 @@ export class WebAdapter {
149
179
  return path === '/' ? '' : path.replace(/^\//, '')
150
180
  }
151
181
 
152
- getPageTitle() {
182
+ getPageTitle(route) {
153
183
  try {
154
- if (typeof document !== 'undefined' && document.title) {
184
+ if (typeof document !== 'undefined' && document.title && document.title !== 'uni-app') {
155
185
  return document.title
156
186
  }
157
187
  } catch (e) {}
188
+ var r = route || this.getCurrentPagePath()
189
+ if (r && ROUTE_TITLE_MAP[r]) {
190
+ return ROUTE_TITLE_MAP[r]
191
+ }
158
192
  return null
159
193
  }
160
194
 
@@ -214,11 +248,13 @@ export class WebAdapter {
214
248
  try {
215
249
  const info = uni.getSystemInfoSync()
216
250
  var ua = (typeof navigator !== 'undefined' ? navigator.userAgent : '').toLowerCase()
251
+ var rawBrand = info.brand || this._judgeBrand(ua, info.model) || ''
252
+ var brand = rawBrand.toUpperCase() === 'OPPO' ? 'OPPO' : rawBrand
217
253
  return {
218
- brand: info.brand || this._judgeBrand(ua, info.model),
254
+ brand: brand,
219
255
  model: info.model || '',
220
- platform: info.platform || '',
221
- system: info.system || '',
256
+ platform: (info.platform || '').toLowerCase() === 'android' ? 'Android' : (info.platform || ''),
257
+ system: (info.system || '').toLowerCase().startsWith('android') ? ('Android ' + (info.system.split(' ')[1] || '')) : (info.system || ''),
222
258
  screenWidth: info.screenWidth || 0,
223
259
  screenHeight: info.screenHeight || 0,
224
260
  userAgent:
@@ -365,27 +401,39 @@ export class WebAdapter {
365
401
  }
366
402
 
367
403
  // [PROJECT-ADAPT] 设备品牌/类型识别
368
- // 当前偏向中国市场手机品牌,国际化项目需扩展
404
+ // 全面覆盖中国市场主流手机品牌(OPPO/一加/真我、vivo/iQOO、小米/Redmi、华为/荣耀、三星等)
369
405
  // 返回的 deviceType 数值需与后台枚举对齐
370
406
  _judgeBrand(ua, model) {
371
407
  if (ua.indexOf('iphone') > -1) return 'iPhone'
372
- if (ua.indexOf('huawei') > -1 || ua.indexOf('honor') > -1) return 'Huawei'
373
- if (ua.indexOf('oppo') > -1 || ua.indexOf('pacm00') > -1) return 'Oppo'
374
- if (ua.indexOf('vivo') > -1) return 'vivo'
408
+ if (ua.indexOf('huawei') > -1 || ua.indexOf('honor') > -1 || ua.indexOf('harmonyos') > -1) return 'Huawei'
409
+ if (ua.indexOf('oppo') > -1 || ua.indexOf('pacm00') > -1 || ua.indexOf('heytap') > -1 ||
410
+ ua.indexOf('oplus') > -1 || ua.indexOf('oneplus') > -1 || ua.indexOf('realme') > -1) return 'Oppo'
411
+ if (ua.indexOf('vivo') > -1 || ua.indexOf('iqoo') > -1) return 'vivo'
375
412
  // 精确匹配小米,避免裸子串 'mi' 命中微信/企业微信 UA 里的 "MicroMessenger"
376
- // (曾把微信内所有非小米安卓机误判成小米)。/\bmi[\s-]?\d/ 匹配 "Mi 11"/"MI9",不匹配 "micromessenger"
377
413
  if (ua.indexOf('xiaomi') > -1 || ua.indexOf('redmi') > -1 ||
378
414
  ua.indexOf('miui') > -1 || /\bmi[\s-]?\d/.test(ua)) return 'mi'
379
415
  if (ua.indexOf('sm-') > -1) return 'Samsung'
380
416
  if (ua.indexOf('lg-') > -1 || ua.indexOf('lm-') > -1) return 'LG'
381
417
 
382
- // UA 未命中时按设备型号前缀兜底识别(微信 webview 常拿不到 UA 品牌,但 uni 能给型号)
418
+ // UA 未命中时按设备型号前缀兜底识别(WebView 常拿不到 UA 品牌,但原生/容器能给型号)
383
419
  var m = (model || '').toString().toUpperCase()
384
420
  if (m) {
385
- if (/^V\d{3,}/.test(m)) return 'vivo' // vivo: V2283A / V2055A ...
386
- if (/^(PA|PB|PC|PD|PE|CPH)/.test(m)) return 'Oppo' // oppo/一加型号前缀
387
- if (/^(MI |MI-|M\d{4}|2\d{6})/.test(m) || m.indexOf('REDMI') > -1) return 'mi'
388
- if (/^(ELS|NOH|LIO|ANA|TAS|VOG|BRQ)/.test(m)) return 'Huawei'
421
+ // OPPO / 一加 / Realme:
422
+ // 国内型号格式多为 P + 字母 + 数字 (如 PKV110, PHY110, PJE110, PGEM10, PFUM10, PBAM00, PACM00 等)
423
+ // 海外型号多为 CPH + 数字,Realme 多为 RMX + 数字
424
+ if (/^P[A-Z]{1,2}\d{2,}/.test(m) || /^(CPH|RMX)\d{3,}/.test(m) ||
425
+ m.indexOf('OPPO') > -1 || m.indexOf('ONEPLUS') > -1 || m.indexOf('REALME') > -1) {
426
+ return 'Oppo'
427
+ }
428
+ // vivo / iQOO:
429
+ if (/^V\d{3,}/.test(m) || m.indexOf('VIVO') > -1 || m.indexOf('IQOO') > -1) return 'vivo'
430
+ // 小米 / Redmi:
431
+ if (/^(MI |MI-|M\d{4}|2\d{4,})/.test(m) || m.indexOf('REDMI') > -1 || m.indexOf('XIAOMI') > -1) return 'mi'
432
+ // 华为 / 荣耀:
433
+ if (/^(ELS|NOH|LIO|ANA|TAS|VOG|BRQ|ALN|HBN|PGT|FRI|MAA|ANY|BND)/.test(m) ||
434
+ m.indexOf('HUAWEI') > -1 || m.indexOf('HONOR') > -1) {
435
+ return 'Huawei'
436
+ }
389
437
  if (m.indexOf('SM-') === 0) return 'Samsung'
390
438
  }
391
439
  return '其他型号手机'
package/plugins/click.js CHANGED
@@ -279,11 +279,26 @@ export class ClickPlugin {
279
279
  // [PROJECT-ADAPT] 设计工具命名过滤
280
280
  // 当前规则基于 Sketch/Figma 中文命名导出,英文命名项目需调整
281
281
  if (/^[\s\S]*(?:编组|矩形|备份|椭圆|路径)[\s\S]*$/.test(text) && text.indexOf('编组') > -1) return ''
282
+ // 符号转为可读语义
283
+ var normalized = this._normalizeSymbolText(text)
284
+ if (normalized !== text) return normalized
282
285
  // 文字太长说明是整个区域的内容,不是按钮文字
283
286
  if (text.length > 30) return ''
284
287
  return text.substring(0, 50)
285
288
  }
286
289
 
290
+ _normalizeSymbolText(text) {
291
+ if (!text) return ''
292
+ var trimmed = text.trim()
293
+ if (trimmed === '▼' || trimmed === '▽') return '展开/下拉'
294
+ if (trimmed === '▲' || trimmed === '△') return '收起'
295
+ if (trimmed === '›' || trimmed === '>' || trimmed === '»') return '查看/更多'
296
+ if (trimmed === '‹' || trimmed === '<' || trimmed === '«') return '返回'
297
+ if (trimmed === '×' || trimmed === '✕') return '关闭'
298
+ if (trimmed === '+') return '添加/增加'
299
+ return text
300
+ }
301
+
287
302
  /**
288
303
  * 检查容器内 image/img 标签的 src,提取文件名
289
304
  */
@@ -296,7 +311,16 @@ export class ClickPlugin {
296
311
  // 提取文件名(去掉路径和后缀)
297
312
  var match = src.match(/\/([^/]+)\.\w+$/)
298
313
  if (match && match[1]) {
299
- return '[img:' + match[1] + ']'
314
+ var name = match[1]
315
+ var lower = name.toLowerCase()
316
+ if (lower.indexOf('back') > -1 || lower === 'label_2') return '返回'
317
+ if (lower.indexOf('close') > -1 || lower.indexOf('cloess') > -1) return '关闭'
318
+ if (lower.indexOf('search') > -1) return '搜索'
319
+ if (lower.indexOf('share') > -1) return '分享'
320
+ if (lower.indexOf('refresh') > -1) return '刷新'
321
+ if (lower.indexOf('cart') > -1) return '购物车'
322
+ if (lower.indexOf('like') > -1 || lower.indexOf('favor') > -1) return '点赞/收藏'
323
+ return '[img:' + name + ']'
300
324
  }
301
325
  return ''
302
326
  }
@@ -113,6 +113,7 @@ export class PageViewPlugin {
113
113
 
114
114
  function getBestTitle() {
115
115
  var t = typeof document !== 'undefined' ? document.title : ''
116
+ if (t === 'uni-app') t = ''
116
117
  if (!t) {
117
118
  try {
118
119
  var pages = getCurrentPages()
@@ -123,6 +124,9 @@ export class PageViewPlugin {
123
124
  }
124
125
  } catch (e) {}
125
126
  }
127
+ if (!t && adapter && typeof adapter.getPageTitle === 'function') {
128
+ t = adapter.getPageTitle(currentPage) || ''
129
+ }
126
130
  return t
127
131
  }
128
132