@tmsfe/tms-core 0.0.1 → 0.0.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/src/report.js ADDED
@@ -0,0 +1,434 @@
1
+ import Request from './request';
2
+ import getLocInstance from './location/index.ts';
3
+ import { getAuthInfo } from './env';
4
+
5
+ const R = new Request();
6
+ const ReportInterval = 3000; // 轮训上报间隔
7
+ const ReportDataQueue = []; // 上报数据队列
8
+ let ReportTaskId = -1; // 轮训上报id
9
+
10
+ let appShowOptions = null; // 设置小程序onShow参数
11
+ function getAppShowOptions() {
12
+ if (appShowOptions === null) {
13
+ try {
14
+ appShowOptions = wx.getLaunchOptionsSync();
15
+ } catch (_) {
16
+ appShowOptions = {};
17
+ }
18
+ }
19
+ return appShowOptions;
20
+ }
21
+
22
+ function getTMS() {
23
+ return getApp({ allowDefault: true }).tms;
24
+ }
25
+
26
+ // 缓存系统信息,避免每次都重新获取系统信息
27
+ let systemPromise = null;
28
+ /**
29
+ * 获取系统信息
30
+ * @private
31
+ * @returns {object} 系统信息
32
+ */
33
+ const getSystemPromise = () => {
34
+ if (systemPromise === null) {
35
+ systemPromise = new Promise((resolve) => {
36
+ wx.getSystemInfo({
37
+ success: (systemInfo) => {
38
+ const { model, version: wxVersion, platform, SDKVersion, host } = systemInfo;
39
+ resolve({ model, wxVersion, platform, SDKVersion, host });
40
+ },
41
+ fail: () => {
42
+ resolve({});
43
+ },
44
+ });
45
+ });
46
+ }
47
+ return systemPromise;
48
+ };
49
+
50
+ let networkPromise = null;
51
+ /**
52
+ * 获取网络信息
53
+ * @private
54
+ * @returns {string} 网络信息
55
+ */
56
+ const getNetworkType = () => {
57
+ if (networkPromise === null) {
58
+ networkPromise = new Promise((resolve) => {
59
+ wx.getNetworkType({
60
+ success: (netInfo) => {
61
+ resolve(netInfo.networkType);
62
+ },
63
+ fail: () => {
64
+ resolve('unknown');
65
+ },
66
+ });
67
+ });
68
+ }
69
+ return networkPromise;
70
+ };
71
+
72
+ let ProvinceInfoCache = null;
73
+ /**
74
+ * 获取省市信息
75
+ * @private
76
+ */
77
+ const getProvinceInfo = () => {
78
+ if (!ProvinceInfoCache) {
79
+ ProvinceInfoCache = new Promise(async (resolve) => {
80
+ try {
81
+ const loc = await getLocInstance().getLocationDetailSilent();
82
+ resolve({
83
+ cityName: loc.cityName,
84
+ province: loc.province,
85
+ });
86
+ } catch (_) {
87
+ // 获取位置失败时,尝试获取本地存储的位置信息
88
+ const loc = getLocInstance().getUserLocation();
89
+ if (loc?.cityName) {
90
+ resolve(loc);
91
+ } else {
92
+ resolve({ cityName: '', province: '' });
93
+ }
94
+ }
95
+ });
96
+ }
97
+ return ProvinceInfoCache;
98
+ };
99
+
100
+ let ProvinceInfoCacheByIp = null;
101
+ /**
102
+ * 调接口获取省市信息
103
+ * @private
104
+ */
105
+ const getProvinceInfoByIp = () => {
106
+ if (!ProvinceInfoCacheByIp) {
107
+ ProvinceInfoCacheByIp = new Promise(async (resolve) => {
108
+ try {
109
+ const loc = await R.post('basic/lbs/decodeip');
110
+ const { city, province } = loc.resData.ad_info;
111
+ resolve({ cityName: city, province });
112
+ } catch (_) {
113
+ resolve({ cityName: '', province: '' });
114
+ }
115
+ });
116
+ }
117
+ return ProvinceInfoCacheByIp;
118
+ };
119
+
120
+ // 用于缓存用户唯一标识userId
121
+ let userId = '';
122
+ /**
123
+ * 获取用户唯一标识userId
124
+ * @private
125
+ * @returns {string} userId
126
+ */
127
+ const getUserId = async () => {
128
+ if (userId) {
129
+ return userId;
130
+ }
131
+
132
+ ({ userId } = await getAuthInfo());
133
+ return userId;
134
+ };
135
+
136
+ let launchFrom = '';
137
+ /**
138
+ * 获取用户启动小程序时的渠道来源值
139
+ * @private
140
+ * @returns {string} 用户启动小程序时的渠道来源值
141
+ */
142
+ const getLaunchFrom = () => {
143
+ if (launchFrom) {
144
+ return launchFrom;
145
+ }
146
+
147
+ try {
148
+ const LaunchOptions = wx.getLaunchOptionsSync() || {};
149
+ const { query = {} } = LaunchOptions;
150
+ launchFrom = (query.from || '');
151
+
152
+ return launchFrom;
153
+ } catch (_) {
154
+ return '';
155
+ }
156
+ };
157
+
158
+ /**
159
+ * 将上报的对象类型的数据转换成数组类型
160
+ * @private
161
+ * @param {object} reportData 需要上报的数据
162
+ * @returns {array} 数组格式的上报数据
163
+ */
164
+ const getReportDataList = (reportData = {}) => {
165
+ const reportList = [];
166
+ reportList.length = 40;
167
+ Object.keys(reportData).forEach((key) => {
168
+ const reportIndex = Number(key);
169
+ reportList[reportIndex] = reportData[key];
170
+ });
171
+
172
+ return reportList;
173
+ };
174
+
175
+ // 小程序版本号
176
+ let verNum = null;
177
+ function getMiniProgramVersion() {
178
+ if (verNum === null) {
179
+ const { miniProgram: { version } = {} } = wx.getAccountInfoSync() || {};
180
+ verNum = version;
181
+ }
182
+ return verNum;
183
+ }
184
+
185
+ /**
186
+ * 格式化上报数据
187
+ * @private
188
+ * @param {object} reportData 需要上报的数据
189
+ * @param {object} deviceData 设备数据(系统信息,网络状况,位置信息)
190
+ * @returns {array} 格式化后的上报数据
191
+ */
192
+ const formatReportData = async (reportData, deviceData) => {
193
+ const [system, netType, provinceInfo] = deviceData;
194
+ const param = Array.isArray(reportData.param) ? reportData.param : getReportDataList(reportData);
195
+ const { client } = await getTMS().getEnvInfo();
196
+
197
+ param[5] = await getUserId();
198
+ param[10] = getMiniProgramVersion(); // 上报小程序版本号
199
+ param[16] = getLaunchFrom(); // 渠道公共参数
200
+ param[17] = param[17] || provinceInfo?.province;
201
+ param[18] = param[18] || provinceInfo?.cityName;
202
+ param[19] = system?.host; // 上报宿主app信息 { appId, env, version }
203
+ param[28] = client;
204
+
205
+ // 默认上报数据
206
+ const defaultReportData = {
207
+ 9: '2',
208
+ 12: netType,
209
+ 13: '2',
210
+ 29: getAppShowOptions().scene, // 打开小程序的场景值
211
+ 33: JSON.stringify(system), // 数据字符串化
212
+ 36: getAppShowOptions(), // 打开小程序的场景值及参数
213
+ };
214
+ // 对部分空数据使用默认数据填充
215
+ Object.keys(defaultReportData).forEach((key) => {
216
+ param[key] = (param[key] !== null && JSON.stringify(param[key])) || defaultReportData[key];
217
+ });
218
+
219
+ // 所有上报数据都转换为字符串
220
+ param.forEach((reportItem, index) => {
221
+ if (reportItem && typeof reportItem !== 'string') {
222
+ param[index] = `${JSON.stringify(reportItem)}`;
223
+ } else {
224
+ const paramItem = param[index];
225
+ param[index] = (paramItem || paramItem === 0) ? `${paramItem}` : '';
226
+ }
227
+ });
228
+
229
+ return param.map(item => (item !== null ? encodeURIComponent(item) : item));
230
+ };
231
+
232
+ /**
233
+ * 格式化上报到小程序后台的数据(自定义分析使用)
234
+ * @private
235
+ * @param {array} data 埋点数据
236
+ * @returns {object} 格式化的数据
237
+ */
238
+ const formatAnalyticsData = async (data) => {
239
+ const analyticsData = {};
240
+ const { 10: version, 29: scene } = data;
241
+ analyticsData[10] = version; // 小程序版本号
242
+ analyticsData[29] = scene; // 小程序场景值
243
+ analyticsData[17] = decodeURIComponent(data[17]); // 用户所在省份
244
+ analyticsData[18] = decodeURIComponent(data[18]); // 用户所在城市
245
+
246
+ // 将30-40位埋点字段补充到上报数据中
247
+ for (let i = 30; i < 41; i += 1) {
248
+ analyticsData[i] = decodeURIComponent(data[i] || '');
249
+ // 如果第33列是设备信息,则忽略
250
+ if (data[33] && data[33].indexOf('model') > -1) {
251
+ analyticsData[33] = '';
252
+ }
253
+ }
254
+ const { client } = await getTMS().getEnvInfo();
255
+
256
+ return { analyticsData, eventName: `${client}_${data[27]}` };
257
+ };
258
+
259
+ /**
260
+ * 上报数据到小程序后台
261
+ * @private
262
+ * @param {array} list 需要上报的数据列表
263
+ * @returns {void}
264
+ */
265
+ const reportAnalytics = (list = []) => {
266
+ try {
267
+ list.forEach((item) => {
268
+ const { eventName, analyticsData } = formatAnalyticsData(item);
269
+ wx.reportAnalytics(eventName, analyticsData);
270
+ });
271
+ } catch (_) {}
272
+ };
273
+
274
+ /**
275
+ * @description report 方法 将上报数据缓存到内存中
276
+ * @name report
277
+ * @param {object} reportData 需要上报的数据
278
+ * @param {boolean} reportNow 是否立即上报
279
+ * @param {boolean} locPoi 是否通过微信接口获取poi
280
+ * @returns {Void} 无返回值
281
+ */
282
+ const cache = (reportData, reportNow, locPoi = true) => {
283
+ // 如果是微信爬虫 | 自动化测试场景,则不进行数据上报
284
+ const DO_NOT_NEED_REPORT_SCENES = [1030, 1129];
285
+ if (DO_NOT_NEED_REPORT_SCENES.includes(getAppShowOptions().scene)) {
286
+ return Promise.resolve();
287
+ }
288
+
289
+ const task = [getSystemPromise(), getNetworkType()];
290
+ if (locPoi) {
291
+ task.push(getProvinceInfo());
292
+ } else {
293
+ task.push(getProvinceInfoByIp());
294
+ }
295
+ return Promise.all(task)
296
+ .then(async (deviceData) => {
297
+ // 先对上报数据进行预处理
298
+ const result = await formatReportData(reportData, deviceData);
299
+ // 将需要上报的数据缓存在队列中
300
+ ReportDataQueue.push(result);
301
+ })
302
+ .then(() => {
303
+ if (reportNow) {
304
+ return sendData();
305
+ }
306
+ return { cache: true };
307
+ });
308
+ };
309
+
310
+ /**
311
+ * 发送数据到服务端
312
+ * @private
313
+ * @returns {promise} 发送请求
314
+ */
315
+ const sendData = async () => {
316
+ // 没有数据时,不发送上报请求
317
+ if (ReportDataQueue.length === 0) {
318
+ return Promise.resolve();
319
+ }
320
+
321
+ const cacheReportData = [...ReportDataQueue];
322
+ return R.post('basic/event/upload', { userId: await getUserId(), batch: cacheReportData })
323
+ .then((res = {}) => {
324
+ const { errCode } = res || {};
325
+ // 已经发送成功,则将本次发送的数据从数据队列中删除
326
+ if (errCode === 0) {
327
+ ReportDataQueue.splice(0, cacheReportData.length);
328
+ reportAnalytics(cacheReportData); // 将数据上报至小程序后台
329
+ }
330
+ });
331
+ };
332
+
333
+ /**
334
+ * 开启轮询上报定时器
335
+ * @private
336
+ * @param {boolean} clearTimerAfterSend 是发送完数据后否清除定时器标识
337
+ * @returns {void}
338
+ */
339
+ const startSendTimer = (clearTimerAfterSend) => {
340
+ ReportTaskId = setInterval(async () => {
341
+ sendData();
342
+ if (clearTimerAfterSend) {
343
+ clearInterval(ReportTaskId);
344
+ }
345
+ }, ReportInterval);
346
+ };
347
+
348
+ /**
349
+ * 停止定时器
350
+ * @private
351
+ * @returns {void}
352
+ */
353
+ const stopSendTimer = () => {
354
+ clearInterval(ReportTaskId);
355
+ };
356
+
357
+ /**
358
+ * 发送上报数据到服务端
359
+ * @private
360
+ * @param {boolean} reportNow 是否立即上报
361
+ * @param {boolean} clearTimerAfterSend 是否发送之后清除定时器
362
+ * @returns {void}
363
+ */
364
+ const report = (reportNow = false, clearTimerAfterSend = false) => {
365
+ if (reportNow) {
366
+ if (ReportDataQueue.length > 0) {
367
+ sendData();
368
+ }
369
+ if (!clearTimerAfterSend) {
370
+ startSendTimer(clearTimerAfterSend);
371
+ } else {
372
+ stopSendTimer();
373
+ }
374
+ } else {
375
+ startSendTimer(clearTimerAfterSend);
376
+ }
377
+ };
378
+
379
+ /**
380
+ * 更新小程序onShow参数
381
+ * @private
382
+ * @param {object} options 小程序onShow参数
383
+ * @returns {void}
384
+ */
385
+ const setAppShowOptions = (options) => {
386
+ appShowOptions = options;
387
+ };
388
+
389
+ /**
390
+ * init之后再监听
391
+ */
392
+ const startListenApp = () => {
393
+ /**
394
+ * 监听小程序onShow事件
395
+ * @private
396
+ */
397
+ wx.onAppShow((options) => {
398
+ // 更新onShow参数
399
+ setAppShowOptions(options);
400
+ // 上报数据
401
+ report(true);
402
+ });
403
+
404
+ /**
405
+ * 监听小程序onHide事件
406
+ * @private
407
+ */
408
+ wx.onAppHide(() => {
409
+ // onHide时立即上报,并清除定时器
410
+ report(true, true);
411
+ });
412
+
413
+ /**
414
+ * 监听小程序路由切换事件
415
+ */
416
+ if (!wx.onAppRoute) {
417
+ return;
418
+ }
419
+ wx.onAppRoute((res) => {
420
+ const { path, query, openType } = res || {};
421
+ cache({ 26: 'H', 27: 'H100', 38: path, 39: JSON.stringify(query), 40: openType });
422
+ cache({ 26: 'H', 27: 'H100', 37: 1, 38: path, 39: JSON.stringify(query), 40: openType }, true);
423
+ wx.reportAnalytics('page_show', {
424
+ path,
425
+ query: JSON.stringify(query),
426
+ opentype: openType,
427
+ });
428
+ });
429
+ };
430
+
431
+ export {
432
+ cache,
433
+ startListenApp,
434
+ };