@rk911/tracker-sdk 1.7.16 → 1.7.17
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/index.js +8 -6
- package/native/android/AndroidTracker.java +103 -17
- package/native/harmony/HarmonyTracker.ets +434 -0
- package/native/ios/RKTrackerSDK.swift +944 -168
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -253,17 +253,19 @@ function _ensureUserId(tracker) {
|
|
|
253
253
|
} catch (e) {}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
// 2. URL query
|
|
257
|
-
if (
|
|
256
|
+
// 2. URL query 跨端注入参数兜底(全面兼容标准 search 与 SPA Hash 路由后的 query)
|
|
257
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
258
258
|
try {
|
|
259
|
-
var search = window.location.search
|
|
259
|
+
var search = (window.location.search || '') + '&' + (window.location.hash || '')
|
|
260
260
|
var matchSeq = search.match(/[?&]_trk_seq=([^&]+)/)
|
|
261
261
|
if (matchSeq) {
|
|
262
262
|
session.setInitialSequence(Number(decodeURIComponent(matchSeq[1])))
|
|
263
263
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
if (!uid) {
|
|
265
|
+
var matchUid = search.match(/[?&]_trk_userId=([^&]+)/)
|
|
266
|
+
if (matchUid) {
|
|
267
|
+
uid = decodeURIComponent(matchUid[1])
|
|
268
|
+
}
|
|
267
269
|
}
|
|
268
270
|
} catch (e) {}
|
|
269
271
|
}
|
|
@@ -190,6 +190,10 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
190
190
|
* 初始化 SDK
|
|
191
191
|
*/
|
|
192
192
|
public void init(Application app, String serverUrl, String deviceId, String userId, String phone, String storeName) {
|
|
193
|
+
init(app, serverUrl, deviceId, userId, phone, storeName, "");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public void init(Application app, String serverUrl, String deviceId, String userId, String phone, String storeName, String userName) {
|
|
193
197
|
this.application = app;
|
|
194
198
|
if (serverUrl != null && !serverUrl.contains("/api/point/task/trackBatch")) {
|
|
195
199
|
if (serverUrl.endsWith("/")) {
|
|
@@ -204,7 +208,7 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
204
208
|
this.userId = userId != null ? userId : "";
|
|
205
209
|
this.phone = phone != null ? phone : "";
|
|
206
210
|
this.storeName = storeName != null ? storeName : "";
|
|
207
|
-
this.userName = "";
|
|
211
|
+
this.userName = userName != null ? userName : "";
|
|
208
212
|
this.sessionId = "android_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 10000);
|
|
209
213
|
|
|
210
214
|
// 初始化 SQLite 本地持久化数据库 (S1 防丢保障)
|
|
@@ -435,7 +439,19 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
435
439
|
event.put("elementText", elementText);
|
|
436
440
|
}
|
|
437
441
|
if (customProperties != null) {
|
|
438
|
-
|
|
442
|
+
JSONObject extra = new JSONObject();
|
|
443
|
+
String baseExtraStr = event.optString("extraData");
|
|
444
|
+
if (!TextUtils.isEmpty(baseExtraStr)) {
|
|
445
|
+
try {
|
|
446
|
+
extra = new JSONObject(baseExtraStr);
|
|
447
|
+
} catch (Exception ignored) {}
|
|
448
|
+
}
|
|
449
|
+
Iterator<String> keys = customProperties.keys();
|
|
450
|
+
while (keys.hasNext()) {
|
|
451
|
+
String k = keys.next();
|
|
452
|
+
extra.put(k, customProperties.opt(k));
|
|
453
|
+
}
|
|
454
|
+
event.put("extraData", extra.toString());
|
|
439
455
|
}
|
|
440
456
|
enqueueEvent(event);
|
|
441
457
|
flushAsync();
|
|
@@ -453,7 +469,19 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
453
469
|
try {
|
|
454
470
|
JSONObject event = createBaseEvent("custom", eventName);
|
|
455
471
|
if (customProperties != null) {
|
|
456
|
-
|
|
472
|
+
JSONObject extra = new JSONObject();
|
|
473
|
+
String baseExtraStr = event.optString("extraData");
|
|
474
|
+
if (!TextUtils.isEmpty(baseExtraStr)) {
|
|
475
|
+
try {
|
|
476
|
+
extra = new JSONObject(baseExtraStr);
|
|
477
|
+
} catch (Exception ignored) {}
|
|
478
|
+
}
|
|
479
|
+
Iterator<String> keys = customProperties.keys();
|
|
480
|
+
while (keys.hasNext()) {
|
|
481
|
+
String k = keys.next();
|
|
482
|
+
extra.put(k, customProperties.opt(k));
|
|
483
|
+
}
|
|
484
|
+
event.put("extraData", extra.toString());
|
|
457
485
|
}
|
|
458
486
|
enqueueEvent(event);
|
|
459
487
|
flushAsync(); // 自定义点位立即上报
|
|
@@ -468,26 +496,40 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
468
496
|
* 旧 $dadian 点位兼容方法 (自动匹配后端双写 point.log)
|
|
469
497
|
*/
|
|
470
498
|
public void trackFrontUrl(final String frontUrl, final String elementId, final String postParam) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
499
|
+
boolean isExposure = elementId != null && (elementId.startsWith("look") || elementId.startsWith("show") || elementId.contains("Exposure") || elementId.contains("Show") || elementId.contains("Bubble"));
|
|
500
|
+
if (!isExposure) {
|
|
501
|
+
final String key = (frontUrl != null ? frontUrl : "") + "|" + (elementId != null ? elementId : "") + "|" + (postParam != null ? postParam : "");
|
|
502
|
+
final long now = System.currentTimeMillis();
|
|
503
|
+
if (key.equals(lastLegacyClickKey) && (now - lastLegacyClickTime < 500)) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
lastLegacyClickKey = key;
|
|
507
|
+
lastLegacyClickTime = now;
|
|
475
508
|
}
|
|
476
|
-
lastLegacyClickKey = key;
|
|
477
|
-
lastLegacyClickTime = now;
|
|
478
509
|
|
|
479
510
|
postBgTask(new Runnable() {
|
|
480
511
|
@Override
|
|
481
512
|
public void run() {
|
|
482
513
|
try {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
514
|
+
// 对齐 tracker-sdk 规范:业务手动精准埋点统一使用 custom 事件类型,令两端一致触发后端双写 (SDK_DESIGN 2.2)
|
|
515
|
+
String eventType = "custom";
|
|
516
|
+
String targetId = !TextUtils.isEmpty(frontUrl) ? frontUrl : elementId;
|
|
517
|
+
JSONObject event = createBaseEvent(eventType, targetId);
|
|
518
|
+
event.put("frontUrl", targetId);
|
|
519
|
+
event.put("elementId", targetId);
|
|
520
|
+
if (postParam != null) {
|
|
521
|
+
event.put("postParam", postParam);
|
|
522
|
+
event.put("extraKey", "param");
|
|
523
|
+
event.put("extraValue", postParam);
|
|
524
|
+
try {
|
|
525
|
+
String baseExtraStr = event.optString("extraData");
|
|
526
|
+
JSONObject extra = TextUtils.isEmpty(baseExtraStr) ? new JSONObject() : new JSONObject(baseExtraStr);
|
|
527
|
+
extra.put("param", postParam);
|
|
528
|
+
extra.put("getParam", postParam);
|
|
529
|
+
extra.put("postParam", postParam);
|
|
530
|
+
event.put("extraData", extra.toString());
|
|
531
|
+
} catch (Exception ignored) {}
|
|
486
532
|
}
|
|
487
|
-
JSONObject event = createBaseEvent(eventType, "legacy_dadian");
|
|
488
|
-
event.put("frontUrl", frontUrl);
|
|
489
|
-
event.put("elementId", elementId);
|
|
490
|
-
if (postParam != null) event.put("postParam", postParam);
|
|
491
533
|
enqueueEvent(event);
|
|
492
534
|
flushAsync();
|
|
493
535
|
} catch (Exception e) {
|
|
@@ -534,8 +576,13 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
534
576
|
if (!TextUtils.isEmpty(error)) {
|
|
535
577
|
extra.put("error", error.length() > 512 ? error.substring(0, 512) : error);
|
|
536
578
|
}
|
|
579
|
+
String reqId = extractRequestIdSafe(reqParam, respData);
|
|
580
|
+
if (!TextUtils.isEmpty(reqId)) {
|
|
581
|
+
extra.put("requestId", reqId);
|
|
582
|
+
}
|
|
537
583
|
if (!TextUtils.isEmpty(phone)) extra.put("phone", phone);
|
|
538
584
|
if (!TextUtils.isEmpty(storeName)) extra.put("storeName", storeName);
|
|
585
|
+
if (!TextUtils.isEmpty(userName)) extra.put("userName", userName);
|
|
539
586
|
if (!TextUtils.isEmpty(userId)) extra.put("userId", userId);
|
|
540
587
|
if (pflag != -1) extra.put("pflag", pflag);
|
|
541
588
|
if (!TextUtils.isEmpty(platformName)) extra.put("platformName", platformName);
|
|
@@ -573,6 +620,38 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
573
620
|
}
|
|
574
621
|
}
|
|
575
622
|
|
|
623
|
+
private static String extractRequestIdSafe(String reqParam, String respData) {
|
|
624
|
+
try {
|
|
625
|
+
if (!TextUtils.isEmpty(reqParam) && reqParam.contains("requestId")) {
|
|
626
|
+
int idx = reqParam.indexOf("\"requestId\"");
|
|
627
|
+
if (idx >= 0) {
|
|
628
|
+
int colon = reqParam.indexOf(':', idx);
|
|
629
|
+
if (colon >= 0) {
|
|
630
|
+
int q1 = reqParam.indexOf('"', colon);
|
|
631
|
+
if (q1 >= 0) {
|
|
632
|
+
int q2 = reqParam.indexOf('"', q1 + 1);
|
|
633
|
+
if (q2 > q1) return reqParam.substring(q1 + 1, q2);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (!TextUtils.isEmpty(respData) && respData.contains("requestId")) {
|
|
639
|
+
int idx = respData.indexOf("\"requestId\"");
|
|
640
|
+
if (idx >= 0) {
|
|
641
|
+
int colon = respData.indexOf(':', idx);
|
|
642
|
+
if (colon >= 0) {
|
|
643
|
+
int q1 = respData.indexOf('"', colon);
|
|
644
|
+
if (q1 >= 0) {
|
|
645
|
+
int q2 = respData.indexOf('"', q1 + 1);
|
|
646
|
+
if (q2 > q1) return respData.substring(q1 + 1, q2);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
} catch (Exception ignored) {}
|
|
652
|
+
return "";
|
|
653
|
+
}
|
|
654
|
+
|
|
576
655
|
public String getNetworkType() {
|
|
577
656
|
try {
|
|
578
657
|
if (application == null) return "";
|
|
@@ -631,6 +710,7 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
631
710
|
JSONObject extra = new JSONObject();
|
|
632
711
|
if (!TextUtils.isEmpty(phone)) extra.put("phone", phone);
|
|
633
712
|
if (!TextUtils.isEmpty(storeName)) extra.put("storeName", storeName);
|
|
713
|
+
if (!TextUtils.isEmpty(userName)) extra.put("userName", userName);
|
|
634
714
|
if (!TextUtils.isEmpty(userId)) extra.put("userId", userId);
|
|
635
715
|
if (pflag != -1) extra.put("pflag", pflag);
|
|
636
716
|
if (!TextUtils.isEmpty(platformName)) extra.put("platformName", platformName);
|
|
@@ -1008,7 +1088,7 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
1008
1088
|
if (TextUtils.isEmpty(elementId)) return "";
|
|
1009
1089
|
String lower = elementId.toLowerCase();
|
|
1010
1090
|
if (lower.equals("ll_page_one")) return "首页";
|
|
1011
|
-
if (lower.equals("ll_page_two")) return "
|
|
1091
|
+
if (lower.equals("ll_page_two")) return "发光者";
|
|
1012
1092
|
if (lower.equals("ll_page_three")) return "小视频";
|
|
1013
1093
|
if (lower.equals("ll_page_four")) return "商城";
|
|
1014
1094
|
if (lower.equals("ll_page_five")) return "我的";
|
|
@@ -1078,6 +1158,12 @@ public class AndroidTracker implements Application.ActivityLifecycleCallbacks {
|
|
|
1078
1158
|
event.put("elementText", elementText.length() > 30 ? elementText.substring(0, 30) : elementText);
|
|
1079
1159
|
}
|
|
1080
1160
|
JSONObject extra = new JSONObject();
|
|
1161
|
+
String baseExtraStr = event.optString("extraData");
|
|
1162
|
+
if (!TextUtils.isEmpty(baseExtraStr)) {
|
|
1163
|
+
try {
|
|
1164
|
+
extra = new JSONObject(baseExtraStr);
|
|
1165
|
+
} catch (Exception ignored) {}
|
|
1166
|
+
}
|
|
1081
1167
|
extra.put("viewType", target.getClass().getName());
|
|
1082
1168
|
extra.put("pageX", rawX);
|
|
1083
1169
|
extra.put("pageY", rawY);
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { http } from '@kit.NetworkKit';
|
|
2
|
+
import { preferences } from '@kit.ArkData';
|
|
3
|
+
import { deviceInfo } from '@kit.BasicServicesKit';
|
|
4
|
+
import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
5
|
+
import { connection } from '@kit.NetworkKit';
|
|
6
|
+
import { PreferencesUtil } from '../../PreferencesUtil';
|
|
7
|
+
import { CommonConstants } from '../http/CommonConstants';
|
|
8
|
+
|
|
9
|
+
export interface TrackerEvent {
|
|
10
|
+
eventId: string;
|
|
11
|
+
eventType: string;
|
|
12
|
+
eventName: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
sequence: number;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
userId?: string;
|
|
17
|
+
pageRoute?: string;
|
|
18
|
+
pageTitle?: string;
|
|
19
|
+
referrer?: string;
|
|
20
|
+
os: string;
|
|
21
|
+
deviceBrand: string;
|
|
22
|
+
deviceModel: string;
|
|
23
|
+
osVersion: string;
|
|
24
|
+
networkType?: string;
|
|
25
|
+
duration?: number;
|
|
26
|
+
elementId?: string;
|
|
27
|
+
elementText?: string;
|
|
28
|
+
frontUrl?: string;
|
|
29
|
+
postParam?: string;
|
|
30
|
+
extraData?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class HarmonyTracker {
|
|
34
|
+
private static instance: HarmonyTracker | null = null;
|
|
35
|
+
private serverUrl: string = 'https://state.renruikeji.cn/api/point/task/trackBatch';
|
|
36
|
+
private appId: string = '22';
|
|
37
|
+
private sessionId: string = '';
|
|
38
|
+
private deviceId: string = '';
|
|
39
|
+
private userId: string = '';
|
|
40
|
+
private phone: string = '';
|
|
41
|
+
private storeName: string = '';
|
|
42
|
+
private userName: string = '';
|
|
43
|
+
private pflag: number = -1;
|
|
44
|
+
private platformName: string = '鸿蒙';
|
|
45
|
+
private sequence: number = 0;
|
|
46
|
+
private currentPageRoute: string = '';
|
|
47
|
+
private referrerPageRoute: string = '';
|
|
48
|
+
private lastPageStartTime: number = 0;
|
|
49
|
+
private eventQueue: Array<TrackerEvent> = [];
|
|
50
|
+
private isFlushing: boolean = false;
|
|
51
|
+
private flushTimerId: number = -1;
|
|
52
|
+
|
|
53
|
+
private static readonly LOG_TAG: string = 'HarmonyTracker';
|
|
54
|
+
private static readonly LOG_DOMAIN: number = 0xFF01;
|
|
55
|
+
private static readonly CACHE_KEY: string = 'rk_tracker_events_cache';
|
|
56
|
+
private static readonly MAX_QUEUE_SIZE: number = 1000;
|
|
57
|
+
private static readonly BATCH_SIZE: number = 20;
|
|
58
|
+
private static readonly FLUSH_INTERVAL_MS: number = 3000;
|
|
59
|
+
|
|
60
|
+
private static readonly friendlyTitles: Record<string, string> = {
|
|
61
|
+
'HomeMainPage': 'APP主页/首页Tab',
|
|
62
|
+
'CircleMainPage': '圈子/健康圈',
|
|
63
|
+
'MallMainPage': '商城/商城首页',
|
|
64
|
+
'VideoMainPage': '小视频推荐',
|
|
65
|
+
'MemberMainPage': '个人中心/我的',
|
|
66
|
+
'WebViewApp': '通用Web容器',
|
|
67
|
+
'Login': '登录页',
|
|
68
|
+
'VoicePunchIn': '语音打卡',
|
|
69
|
+
'HealthMascot': '健康助手'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
private constructor() {
|
|
73
|
+
this.sessionId = 'harmony_' + Date.now() + '_' + Math.floor(Math.random() * 9000 + 1000);
|
|
74
|
+
this.deviceId = 'harmony_dev_' + Math.floor(Math.random() * 1000000000);
|
|
75
|
+
this.loadCachedEvents();
|
|
76
|
+
this.startTimer();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public static getInstance(): HarmonyTracker {
|
|
80
|
+
if (!HarmonyTracker.instance) {
|
|
81
|
+
HarmonyTracker.instance = new HarmonyTracker();
|
|
82
|
+
}
|
|
83
|
+
return HarmonyTracker.instance;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public init(serverUrl?: string, deviceId?: string, userId?: string, phone?: string, storeName?: string, userName?: string): void {
|
|
87
|
+
if (serverUrl) {
|
|
88
|
+
if (!serverUrl.includes('/api/point/task/trackBatch')) {
|
|
89
|
+
this.serverUrl = serverUrl.endsWith('/') ? serverUrl + 'api/point/task/trackBatch' : serverUrl + '/api/point/task/trackBatch';
|
|
90
|
+
} else {
|
|
91
|
+
this.serverUrl = serverUrl;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (deviceId) this.deviceId = deviceId;
|
|
95
|
+
if (userId) this.userId = userId;
|
|
96
|
+
if (phone) this.phone = phone;
|
|
97
|
+
if (storeName) this.storeName = storeName;
|
|
98
|
+
if (userName) this.userName = userName;
|
|
99
|
+
this.flushAsync();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public updateUser(userId?: string, phone?: string, storeName?: string, userName?: string): void {
|
|
103
|
+
if (userId !== undefined) this.userId = userId;
|
|
104
|
+
if (phone !== undefined) this.phone = phone;
|
|
105
|
+
if (storeName !== undefined) this.storeName = storeName;
|
|
106
|
+
if (userName !== undefined) this.userName = userName;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public setPlatform(pflag: number, platformName?: string): void {
|
|
110
|
+
this.pflag = pflag;
|
|
111
|
+
if (platformName) this.platformName = platformName;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public resetSession(): void {
|
|
115
|
+
this.sequence = 0;
|
|
116
|
+
this.sessionId = 'harmony_' + Date.now() + '_' + Math.floor(Math.random() * 9000 + 1000);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public getSessionId(): string { return this.sessionId; }
|
|
120
|
+
public getDeviceId(): string { return this.deviceId; }
|
|
121
|
+
public getUserId(): string { return this.userId; }
|
|
122
|
+
public getPhone(): string { return this.phone; }
|
|
123
|
+
public getStoreName(): string { return this.storeName; }
|
|
124
|
+
public getUserName(): string { return this.userName; }
|
|
125
|
+
public getPflag(): number { return this.pflag; }
|
|
126
|
+
public getPlatformName(): string { return this.platformName; }
|
|
127
|
+
public getCurrentSequence(): number { return this.sequence; }
|
|
128
|
+
|
|
129
|
+
public getFriendlyTitle(route: string): string {
|
|
130
|
+
if (HarmonyTracker.friendlyTitles[route]) return HarmonyTracker.friendlyTitles[route];
|
|
131
|
+
for (const key in HarmonyTracker.friendlyTitles) {
|
|
132
|
+
if (route.includes(key)) return HarmonyTracker.friendlyTitles[key];
|
|
133
|
+
}
|
|
134
|
+
return route;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public getNativeContextJson(): string {
|
|
138
|
+
const ctx: Record<string, Object> = {
|
|
139
|
+
'appId': this.appId,
|
|
140
|
+
'deviceId': this.deviceId,
|
|
141
|
+
'userId': this.userId,
|
|
142
|
+
'phone': this.phone,
|
|
143
|
+
'sessionId': this.sessionId,
|
|
144
|
+
'appVersion': '1.0.0',
|
|
145
|
+
'platform': 'harmony',
|
|
146
|
+
'os': 'HarmonyOS',
|
|
147
|
+
'brand': deviceInfo.brand || 'HUAWEI',
|
|
148
|
+
'deviceBrand': deviceInfo.brand || 'HUAWEI',
|
|
149
|
+
'model': deviceInfo.productModel || '',
|
|
150
|
+
'deviceModel': deviceInfo.productModel || '',
|
|
151
|
+
'osVersion': deviceInfo.osFullName || '',
|
|
152
|
+
'currentSequence': this.sequence,
|
|
153
|
+
'pflag': this.pflag,
|
|
154
|
+
'pFlagName': this.platformName
|
|
155
|
+
};
|
|
156
|
+
return JSON.stringify(ctx);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public track(eventName: string, properties?: Record<string, Object>): void {
|
|
160
|
+
this.trackCustom(eventName, '', properties);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public trackCustom(eventName: string, elementText?: string, properties?: Record<string, Object>): void {
|
|
164
|
+
const event = this.createBaseEvent('custom', eventName);
|
|
165
|
+
if (elementText) event.elementText = elementText;
|
|
166
|
+
if (properties) {
|
|
167
|
+
const extra = this.buildBaseExtra();
|
|
168
|
+
for (const k in properties) extra[k] = properties[k];
|
|
169
|
+
event.extraData = JSON.stringify(extra);
|
|
170
|
+
}
|
|
171
|
+
this.enqueueEvent(event);
|
|
172
|
+
this.flushAsync();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public trackPageView(pageRoute: string, pageTitle?: string): void {
|
|
176
|
+
if (pageRoute === this.currentPageRoute && (Date.now() - this.lastPageStartTime < 500)) return;
|
|
177
|
+
this.referrerPageRoute = this.currentPageRoute;
|
|
178
|
+
this.currentPageRoute = pageRoute;
|
|
179
|
+
this.lastPageStartTime = Date.now();
|
|
180
|
+
|
|
181
|
+
const title = pageTitle && pageTitle.length > 0 ? pageTitle : this.getFriendlyTitle(pageRoute);
|
|
182
|
+
const event = this.createBaseEvent('page_view', 'page_view');
|
|
183
|
+
event.pageRoute = pageRoute;
|
|
184
|
+
event.pageTitle = title;
|
|
185
|
+
this.enqueueEvent(event);
|
|
186
|
+
this.flushAsync();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public trackPageStay(pageRoute: string, pageTitle?: string, durationMs?: number): void {
|
|
190
|
+
const dur = durationMs ? durationMs : (Date.now() - this.lastPageStartTime);
|
|
191
|
+
if (dur < 500) return;
|
|
192
|
+
const title = pageTitle && pageTitle.length > 0 ? pageTitle : this.getFriendlyTitle(pageRoute);
|
|
193
|
+
const event = this.createBaseEvent('page_stay', 'page_stay');
|
|
194
|
+
event.pageRoute = pageRoute;
|
|
195
|
+
event.pageTitle = title;
|
|
196
|
+
event.duration = dur;
|
|
197
|
+
this.enqueueEvent(event);
|
|
198
|
+
this.flushAsync();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public trackVideo(eventName: string, videoId: number, videoTitle?: string, customProperties?: Record<string, Object>): void {
|
|
202
|
+
const event = this.createBaseEvent('video', eventName);
|
|
203
|
+
event.elementId = 'video_' + videoId;
|
|
204
|
+
if (videoTitle) event.elementText = videoTitle;
|
|
205
|
+
const extra = this.buildBaseExtra();
|
|
206
|
+
extra['videoId'] = videoId;
|
|
207
|
+
if (videoTitle) extra['videoTitle'] = videoTitle;
|
|
208
|
+
if (customProperties) {
|
|
209
|
+
for (const k in customProperties) extra[k] = customProperties[k];
|
|
210
|
+
if (typeof customProperties['playDurationMs'] === 'number') {
|
|
211
|
+
event.duration = customProperties['playDurationMs'] as number;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
event.extraData = JSON.stringify(extra);
|
|
215
|
+
this.enqueueEvent(event);
|
|
216
|
+
this.flushAsync();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public trackApi(url: string, method: string, statusCode: number, duration: number, reqParam?: string, respData?: string, error?: string): void {
|
|
220
|
+
if (!url || url.includes('/api/point/task/trackBatch')) return;
|
|
221
|
+
const isError = statusCode >= 400 || (error !== undefined && error !== null && error.length > 0);
|
|
222
|
+
const eventType = isError ? 'api_error' : 'api_request';
|
|
223
|
+
const apiPath = this.extractApiPath(url);
|
|
224
|
+
const event = this.createBaseEvent(eventType, apiPath);
|
|
225
|
+
|
|
226
|
+
const extra = this.buildBaseExtra();
|
|
227
|
+
extra['url'] = url;
|
|
228
|
+
extra['apiPath'] = apiPath;
|
|
229
|
+
extra['method'] = (method || 'GET').toUpperCase();
|
|
230
|
+
extra['statusCode'] = statusCode;
|
|
231
|
+
extra['duration'] = duration;
|
|
232
|
+
if (reqParam) extra['requestParams'] = reqParam.substring(0, 2048);
|
|
233
|
+
if (respData) extra['responseData'] = respData.substring(0, 2048);
|
|
234
|
+
if (error) extra['error'] = error.substring(0, 512);
|
|
235
|
+
|
|
236
|
+
event.extraData = JSON.stringify(extra);
|
|
237
|
+
this.enqueueEvent(event);
|
|
238
|
+
if (isError) this.flushAsync();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public trackFrontUrl(frontUrl: string, elementId?: string, postParam?: string): void {
|
|
242
|
+
let eventType = 'click';
|
|
243
|
+
if (elementId && (elementId.startsWith('look') || elementId.startsWith('show') || elementId.includes('Exposure'))) {
|
|
244
|
+
eventType = 'exposure';
|
|
245
|
+
}
|
|
246
|
+
const event = this.createBaseEvent(eventType, 'legacy_dadian');
|
|
247
|
+
event.frontUrl = frontUrl;
|
|
248
|
+
if (elementId) event.elementId = elementId;
|
|
249
|
+
if (postParam) event.postParam = postParam;
|
|
250
|
+
this.enqueueEvent(event);
|
|
251
|
+
this.flushAsync();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private createBaseEvent(eventType: string, eventName: string): TrackerEvent {
|
|
255
|
+
this.sequence++;
|
|
256
|
+
const evt: TrackerEvent = {
|
|
257
|
+
eventId: 'e_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
|
|
258
|
+
eventType: eventType,
|
|
259
|
+
eventName: eventName,
|
|
260
|
+
timestamp: Date.now(),
|
|
261
|
+
sequence: this.sequence,
|
|
262
|
+
sessionId: this.sessionId,
|
|
263
|
+
os: 'HarmonyOS',
|
|
264
|
+
deviceBrand: deviceInfo.brand || 'HUAWEI',
|
|
265
|
+
deviceModel: deviceInfo.productModel || '',
|
|
266
|
+
osVersion: deviceInfo.osFullName || '',
|
|
267
|
+
networkType: this.getNetworkType()
|
|
268
|
+
};
|
|
269
|
+
if (this.userId) evt.userId = this.userId;
|
|
270
|
+
if (this.currentPageRoute) {
|
|
271
|
+
evt.pageRoute = this.currentPageRoute;
|
|
272
|
+
evt.pageTitle = this.getFriendlyTitle(this.currentPageRoute);
|
|
273
|
+
}
|
|
274
|
+
if (this.referrerPageRoute) evt.referrer = this.referrerPageRoute;
|
|
275
|
+
|
|
276
|
+
const baseExtra = this.buildBaseExtra();
|
|
277
|
+
evt.extraData = JSON.stringify(baseExtra);
|
|
278
|
+
return evt;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private buildBaseExtra(): Record<string, Object> {
|
|
282
|
+
const extra: Record<string, Object> = { 'platform': 'harmony' };
|
|
283
|
+
if (this.phone) extra['phone'] = this.phone;
|
|
284
|
+
if (this.storeName) extra['storeName'] = this.storeName;
|
|
285
|
+
if (this.userId) extra['userId'] = this.userId;
|
|
286
|
+
if (this.pflag !== -1) extra['pflag'] = this.pflag;
|
|
287
|
+
if (this.platformName) extra['platformName'] = this.platformName;
|
|
288
|
+
return extra;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private extractApiPath(url: string): string {
|
|
292
|
+
try {
|
|
293
|
+
const qIdx = url.indexOf('?');
|
|
294
|
+
const clean = qIdx >= 0 ? url.substring(0, qIdx) : url;
|
|
295
|
+
const schemeIdx = clean.indexOf('://');
|
|
296
|
+
if (schemeIdx >= 0) {
|
|
297
|
+
const pathIdx = clean.indexOf('/', schemeIdx + 3);
|
|
298
|
+
if (pathIdx >= 0) return clean.substring(pathIdx);
|
|
299
|
+
}
|
|
300
|
+
return clean;
|
|
301
|
+
} catch (e) {
|
|
302
|
+
return url;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private getNetworkType(): string {
|
|
307
|
+
try {
|
|
308
|
+
if (!connection.hasDefaultNetSync()) return '';
|
|
309
|
+
const netHandle = connection.getDefaultNetSync();
|
|
310
|
+
const cap = connection.getNetCapabilitiesSync(netHandle);
|
|
311
|
+
if (cap && cap.bearerTypes) {
|
|
312
|
+
if (cap.bearerTypes.includes(connection.NetBearType.BEARER_WIFI)) return 'wifi';
|
|
313
|
+
if (cap.bearerTypes.includes(connection.NetBearType.BEARER_CELLULAR)) return '4g';
|
|
314
|
+
if (cap.bearerTypes.includes(connection.NetBearType.BEARER_ETHERNET)) return 'ethernet';
|
|
315
|
+
}
|
|
316
|
+
return 'other';
|
|
317
|
+
} catch (e) {
|
|
318
|
+
return '';
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private enqueueEvent(event: TrackerEvent): void {
|
|
323
|
+
this.eventQueue.push(event);
|
|
324
|
+
if (this.eventQueue.length > HarmonyTracker.MAX_QUEUE_SIZE) {
|
|
325
|
+
this.eventQueue.splice(0, this.eventQueue.length - HarmonyTracker.MAX_QUEUE_SIZE);
|
|
326
|
+
}
|
|
327
|
+
this.saveQueueToDisk();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private async saveQueueToDisk(): Promise<void> {
|
|
331
|
+
try {
|
|
332
|
+
const util = new PreferencesUtil();
|
|
333
|
+
await util.saveCache(HarmonyTracker.CACHE_KEY, JSON.stringify(this.eventQueue));
|
|
334
|
+
} catch (e) {}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private async loadCachedEvents(): Promise<void> {
|
|
338
|
+
try {
|
|
339
|
+
const util = new PreferencesUtil();
|
|
340
|
+
const cache = await util.getCache(HarmonyTracker.CACHE_KEY);
|
|
341
|
+
if (cache && typeof cache === 'string') {
|
|
342
|
+
const parsed = JSON.parse(cache) as Array<TrackerEvent>;
|
|
343
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
344
|
+
this.eventQueue = parsed.concat(this.eventQueue);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private startTimer(): void {
|
|
351
|
+
if (this.flushTimerId !== -1) return;
|
|
352
|
+
this.flushTimerId = setInterval(() => {
|
|
353
|
+
this.flushAsync();
|
|
354
|
+
}, HarmonyTracker.FLUSH_INTERVAL_MS);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
public flushAsync(): void {
|
|
358
|
+
if (this.isFlushing || this.eventQueue.length === 0) return;
|
|
359
|
+
this.flushInternal();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
public async flushSync(): Promise<void> {
|
|
363
|
+
await this.flushInternal();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private async flushInternal(): Promise<void> {
|
|
367
|
+
if (this.isFlushing || this.eventQueue.length === 0) return;
|
|
368
|
+
this.isFlushing = true;
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
let maxBatches = 5;
|
|
372
|
+
while (maxBatches-- > 0 && this.eventQueue.length > 0) {
|
|
373
|
+
const batch = this.eventQueue.slice(0, HarmonyTracker.BATCH_SIZE);
|
|
374
|
+
const success = await this.sendBatch(batch);
|
|
375
|
+
if (success) {
|
|
376
|
+
this.eventQueue.splice(0, batch.length);
|
|
377
|
+
await this.saveQueueToDisk();
|
|
378
|
+
} else {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} finally {
|
|
383
|
+
this.isFlushing = false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private async sendBatch(records: Array<TrackerEvent>): Promise<boolean> {
|
|
388
|
+
if (!this.serverUrl || records.length === 0) return false;
|
|
389
|
+
const httpRequest = http.createHttp();
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
let batchSessionId = this.sessionId;
|
|
393
|
+
let batchUserId = this.userId;
|
|
394
|
+
if (records[0].sessionId) batchSessionId = records[0].sessionId;
|
|
395
|
+
if (records[0].userId) batchUserId = records[0].userId;
|
|
396
|
+
|
|
397
|
+
const body: Record<string, Object> = {
|
|
398
|
+
'appId': parseInt(this.appId, 10),
|
|
399
|
+
'sessionId': batchSessionId,
|
|
400
|
+
'deviceId': this.deviceId,
|
|
401
|
+
'userId': batchUserId,
|
|
402
|
+
'phone': this.phone,
|
|
403
|
+
'storeName': this.storeName,
|
|
404
|
+
'userName': this.userName,
|
|
405
|
+
'platform': 'harmony',
|
|
406
|
+
'events': records
|
|
407
|
+
};
|
|
408
|
+
if (this.pflag !== -1) body['pflag'] = this.pflag;
|
|
409
|
+
if (this.platformName) body['platformName'] = this.platformName;
|
|
410
|
+
|
|
411
|
+
const bodyStr = JSON.stringify(body);
|
|
412
|
+
const options: http.HttpRequestOptions = {
|
|
413
|
+
method: http.RequestMethod.POST,
|
|
414
|
+
header: {
|
|
415
|
+
'Content-Type': 'application/json; charset=UTF-8',
|
|
416
|
+
'appId': this.appId,
|
|
417
|
+
'deviceId': this.deviceId
|
|
418
|
+
},
|
|
419
|
+
extraData: bodyStr,
|
|
420
|
+
expectDataType: http.HttpDataType.STRING,
|
|
421
|
+
connectTimeout: 5000,
|
|
422
|
+
readTimeout: 5000
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const resp = await httpRequest.request(this.serverUrl, options);
|
|
426
|
+
return resp.responseCode >= 200 && resp.responseCode < 300;
|
|
427
|
+
} catch (e) {
|
|
428
|
+
hilog.error(HarmonyTracker.LOG_DOMAIN, HarmonyTracker.LOG_TAG, 'sendBatch failed: %{public}s', JSON.stringify(e));
|
|
429
|
+
return false;
|
|
430
|
+
} finally {
|
|
431
|
+
httpRequest.destroy();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|