@rk911/tracker-sdk 1.7.16 → 1.7.18

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.
@@ -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
- event.put("extraData", customProperties.toString());
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
- event.put("extraData", customProperties.toString());
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
- final String key = (frontUrl != null ? frontUrl : "") + "|" + (elementId != null ? elementId : "");
472
- final long now = System.currentTimeMillis();
473
- if (key.equals(lastLegacyClickKey) && (now - lastLegacyClickTime < 500)) {
474
- return;
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
- String eventType = "click";
484
- if (elementId != null && (elementId.startsWith("look") || elementId.contains("Exposure") || elementId.contains("Show") || elementId.contains("Bubble"))) {
485
- eventType = "exposure";
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
+ }