@tmsfe/tms-core 0.0.38 → 0.0.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmsfe/tms-core",
3
- "version": "0.0.38",
3
+ "version": "0.0.41",
4
4
  "description": "tms运行时框架",
5
5
  "repository": {
6
6
  "type": "git",
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import syncApi from './syncfnmanager';
9
+ import AutoReport from './report/proxy/index';
9
10
  import md5 from './md5';
10
11
  import { rpxToPx } from './rpx';
11
12
  import { serialize } from './objUtils';
@@ -39,6 +40,7 @@ function invoke(obj, funcName, args) {
39
40
  function initProxy(appObj, options) {
40
41
  app = appObj;
41
42
  initOptions = options;
43
+ AutoReport.init();
42
44
  }
43
45
 
44
46
  function awaitTMS() {
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import Request from './request';
2
+ import AutoReport from './report/proxy/index';
2
3
  import Reporter from './report/index';
3
4
  import { getConfig } from './config';
4
5
  import syncApi from './syncfnmanager';
@@ -101,6 +102,7 @@ const init = (options = {}) => {
101
102
  setEnvInfo(envInfo);
102
103
  setAppPagePaths(appPagePaths, homePage);
103
104
  Reporter.init(envInfo);
105
+ AutoReport.init();
104
106
  Request.defaultHost = defaultHost;
105
107
  // 初始化云环境
106
108
  wx.cloud.init({ env: cloudEnvId });
@@ -9,13 +9,15 @@ import helper from './helper';
9
9
  * 获取埋点的基础字段
10
10
  */
11
11
  function getBaseData(deviceData: IDeviceData): DataItem[] {
12
- const arr = new Array<string>(helper.dataArrLen);
13
12
  const { networkType, location } = deviceData;
14
13
  const { appVersion, client } = helper.getInitOptions();
14
+ const { host } = helper.getSystemInfo();
15
+ const arr = new Array<string>(helper.dataArrLen);
15
16
  // todo: 如何区分新旧埋点?新:f20不为空,旧:f20为空
16
17
  // ++++++++++++++++++++++++++字段列表++++++++++++++++++++++++++
17
18
  // 0: log_time,日志入库时间
18
- // 1: access_time,用户点击时间,服务端统一处理
19
+ // 1: access_time,埋点触发时间
20
+ arr[1] = Date.now().toString();
19
21
  // 2: user_ip,前端无需赋值
20
22
  // 3: qimei,灯塔中的用户ID
21
23
  // 4: imei,Android手机的imei IOS系统中的idfv 车联网中的wecarid
@@ -42,14 +44,14 @@ function getBaseData(deviceData: IDeviceData): DataItem[] {
42
44
  // 18: f18,city - 城市
43
45
  arr[18] = location.cityName;
44
46
  // 19: f19,当前小程序运行的宿主环境
45
- arr[19] = helper.getSystemInfo().host;
47
+ arr[19] = helper.convert2String(host);
46
48
  // 28: f28,sinan、mycar等
47
49
  arr[28] = client;
48
50
  // 29: f29,小程序场景值
49
51
  arr[29] = helper.getAppScene();
50
52
  // 33: f33,系统信息
51
53
  arr[33] = helper.getSystemInfoString();
52
- // 36: f36,小程序启动时的参数
54
+ // 36: f36,小程序启动时的url和参数
53
55
  arr[36] = helper.getLaunchOptionsString();
54
56
  // --------------------------字段列表--------------------------
55
57
  return arr;
@@ -83,7 +85,7 @@ function formatData(data: IOldParams): Promise<DataItem[]> {
83
85
  * 格式化快速上报埋点数据,不依赖用户位置
84
86
  */
85
87
  function formatFastData(data: IOldParams): DataItem[] {
86
- return jointData(data, helper.defaultDevice);
88
+ return jointData(data, helper.getCacheDeviceData());
87
89
  }
88
90
 
89
91
  export default {
@@ -16,7 +16,8 @@ function getBaseData(deviceData: IDeviceData): { arr: DataItem[], nextIndex: num
16
16
  // todo: 如何区分新旧埋点?新:f20不为空,旧:f20为空
17
17
  // ++++++++++++++++++++++++++字段列表++++++++++++++++++++++++++
18
18
  // 0: log_time,日志入库时间
19
- // 1: access_time,用户点击时间,服务端统一处理
19
+ // 1: access_time,埋点触发时间
20
+ arr[1] = Date.now().toString();
20
21
  // 2: user_ip,前端无需赋值
21
22
  // 3: qimei,灯塔中的用户ID
22
23
  // 4: imei,Android手机的imei IOS系统中的idfv 车联网中的wecarid
@@ -47,7 +48,7 @@ function getBaseData(deviceData: IDeviceData): { arr: DataItem[], nextIndex: num
47
48
  arr[20] = client;
48
49
  // 21: f21,小程序场景值
49
50
  arr[21] = helper.getAppScene();
50
- // 22: f22,小程序启动时的参数
51
+ // 22: f22,小程序启动时的url和参数
51
52
  arr[22] = helper.getLaunchOptionsString();
52
53
  // 23: f23,当前页面的url
53
54
  arr[23] = page.route;
@@ -92,7 +93,7 @@ function formatData(data: any[]): Promise<DataItem[]> {
92
93
  * 格式化快速上报埋点数据,不依赖用户位置
93
94
  */
94
95
  function formatFastData(data: any[]): DataItem[] {
95
- return jointData(data, helper.defaultDevice);
96
+ return jointData(data, helper.getCacheDeviceData());
96
97
  }
97
98
 
98
99
  export default {
@@ -18,6 +18,16 @@ let initOptions: IInitOptions;
18
18
  function init(options: IInitOptions): void {
19
19
  if (!initOptions) {
20
20
  initOptions = options;
21
+
22
+ // 从上次缓存中读取,方便首次Report可以先带上
23
+ const loc = wx.getStorageSync('home.location_city') || wx.getStorageSync('home.city');
24
+ deviceData = {
25
+ networkType: '',
26
+ location: {
27
+ province: loc?.province as string,
28
+ cityName: loc?.cityName as string,
29
+ },
30
+ };
21
31
  }
22
32
  }
23
33
 
@@ -37,9 +47,8 @@ function getSystemInfo(): ISystemInfo {
37
47
  if (systemInfo === null) {
38
48
  const system = syncApi.getSystemInfoSync() as any;
39
49
  // eslint-disable-next-line
40
- const { model = '', version: wxVersion = '', platform = '', SDKVersion = '', host } = system;
41
- const hostStr = host ? JSON.stringify(host) : '';
42
- systemInfo = { model, wxVersion, platform, SDKVersion, host: hostStr };
50
+ const { model = '', version: wxVersion = '', platform = '', SDKVersion = '', host = '' } = system;
51
+ systemInfo = { model, wxVersion, platform, SDKVersion, host };
43
52
  }
44
53
  return systemInfo;
45
54
  }
@@ -96,8 +105,8 @@ let launchOptions: string | null = null;
96
105
  */
97
106
  function getLaunchOptionsString(): string {
98
107
  if (launchOptions === null) {
99
- const obj = syncApi.getLaunchOptionsSync() as any;
100
- launchOptions = obj.query ? JSON.stringify(obj.query) : '';
108
+ const obj = syncApi.getLaunchOptionsSync();
109
+ launchOptions = JSON.stringify(obj);
101
110
  }
102
111
  return launchOptions;
103
112
  }
@@ -107,7 +116,7 @@ function getLaunchOptionsString(): string {
107
116
  */
108
117
  function getLaunchFrom(): string {
109
118
  const obj = syncApi.getLaunchOptionsSync() as any;
110
- return obj.query?.from as string || '';
119
+ return obj.query?.from;
111
120
  }
112
121
 
113
122
  /**
@@ -118,11 +127,6 @@ function getAppScene(): string {
118
127
  return scene.toString();
119
128
  }
120
129
 
121
- // 给非空字段都encodeURIComponent一下
122
- function encodeItems(arr: DataItem[]): DataItem[] {
123
- return arr.map(t => (t ? encodeURIComponent(t) : t));
124
- }
125
-
126
130
  // 是否爬虫
127
131
  let isCrawler: boolean | null = null;
128
132
 
@@ -147,46 +151,8 @@ function canReport(): boolean {
147
151
  */
148
152
  const dataArrLen = 41;
149
153
 
150
- const defaultDevice = {
151
- networkType: null,
152
- location: {
153
- province: null,
154
- cityName: null,
155
- },
156
- } as any as IDeviceData;
157
-
158
- let networkPromise: Promise<string> | null = null;
159
-
160
- function getNetworkType(): Promise<string> {
161
- if (networkPromise === null) {
162
- networkPromise = new Promise((resolve) => {
163
- wx.getNetworkType({
164
- success: (res: any) => resolve(res.networkType),
165
- fail: () => resolve('unknown'),
166
- });
167
- });
168
- }
169
- return networkPromise;
170
- }
171
-
172
- let locPromise: Promise<ILocation> | null = null;
173
-
174
- function getLocation(): Promise<ILocation> {
175
- if (locPromise === null) {
176
- locPromise = new Promise<ILocation>((resolve) => {
177
- const LOC = getTms().getLocationManager();
178
- LOC.getIpLocation()
179
- .then((res: any) => {
180
- const ad = res.ad_info;
181
- resolve({ province: ad.province, cityName: ad.city });
182
- })
183
- .catch(() => {
184
- resolve({ province: '', cityName: '' });
185
- });
186
- });
187
- }
188
- return locPromise;
189
- }
154
+ // 缓存设备等信息,方便FastReport使用
155
+ let deviceData: IDeviceData;
190
156
 
191
157
  let devicePromise: Promise<IDeviceData> | null = null;
192
158
 
@@ -196,19 +162,48 @@ let devicePromise: Promise<IDeviceData> | null = null;
196
162
  function getDeviceData(): Promise<IDeviceData> {
197
163
  if (devicePromise === null) {
198
164
  devicePromise = new Promise<IDeviceData>((resolve) => {
199
- Promise.all([getNetworkType(), getLocation()]).then((res) => {
200
- const [networkType, location] = res;
201
- resolve({ networkType, location });
165
+ const networkPromise = new Promise<void>((resolve) => {
166
+ wx.getNetworkType({
167
+ success: (res: any) => {
168
+ deviceData.networkType = res.networkType;
169
+ resolve();
170
+ },
171
+ fail: () => resolve(),
172
+ });
173
+ });
174
+
175
+ const locPromise = new Promise<void>((resolve) => {
176
+ // 首次如果有缓存则先用缓存,请求后再更新给下次用
177
+ if (deviceData.location.province) {
178
+ resolve();
179
+ }
180
+ const LOC = getTms().getLocationManager();
181
+ LOC.getIpLocation()
182
+ .then((res: any) => {
183
+ const ad = res.ad_info;
184
+ deviceData.location.province = ad.province;
185
+ deviceData.location.cityName = ad.city;
186
+ resolve();
187
+ })
188
+ .catch(() => resolve());
202
189
  });
190
+
191
+ Promise.all([networkPromise, locPromise]).then(() => resolve(deviceData));
203
192
  });
204
193
  }
205
194
  return devicePromise;
206
195
  }
207
196
 
197
+ /**
198
+ * 获取缓存的设备信息(网络状况,位置信息)
199
+ */
200
+ function getCacheDeviceData(): IDeviceData {
201
+ return deviceData;
202
+ }
203
+
208
204
  export default {
209
205
  init,
210
206
  getTms,
211
- encodeItems,
212
207
  getInitOptions,
213
208
  getSystemInfo,
214
209
  getSystemInfoString,
@@ -219,6 +214,6 @@ export default {
219
214
  getAppScene,
220
215
  canReport,
221
216
  dataArrLen,
222
- defaultDevice,
223
217
  getDeviceData,
218
+ getCacheDeviceData,
224
219
  };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 负责组件的全埋点
3
+ */
4
+
5
+ import helper from './helper';
6
+
7
+ let originalComponent: any = null;
8
+
9
+ // 劫持其他事件
10
+ function proxyHandle(componentName: string, methods: any, methodName: string): void {
11
+ const original = methods[methodName];
12
+ // eslint-disable-next-line no-param-reassign
13
+ methods[methodName] = function (...args: any[]): any {
14
+ // 执行原函数之后再发埋点
15
+ return helper.executeFunc(this, original, args, () => {
16
+ const data = helper.serializeData(this.data);
17
+ helper.reportData(`Component_${componentName}`, methodName, data);
18
+ });
19
+ };
20
+ }
21
+
22
+ // 劫持Page
23
+ function init(): void {
24
+ // @ts-ignore
25
+ originalComponent = Component;
26
+ // @ts-ignore
27
+ Component = function (options: any) {
28
+ // 只有tms的页面才需要上报
29
+ if (options.tmsAutoReport) {
30
+ // tmsReportEvents是由工具在编译时分析出的要上报的事件列表
31
+ const methods = options.methods || {};
32
+ for (const name of options.tmsReportEvents) {
33
+ proxyHandle(options.tmsComponentName, methods, name);
34
+ }
35
+ }
36
+ originalComponent(options);
37
+ };
38
+ }
39
+
40
+ export default {
41
+ init,
42
+ };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * 自动埋点辅助类
3
+ */
4
+
5
+ let reporter: any = null;
6
+
7
+ function reportData(...args: any[]): any {
8
+ if (reporter === null) {
9
+ // 如果是在app.js的onLaunch中调用,则没有getApp().tms为空
10
+ // @ts-ignore
11
+ const tms = getApp()?.tms || wx.tms;
12
+ reporter = tms.getReporter();
13
+ }
14
+ // console.log('自动埋点:', ...args);
15
+ reporter.report2(...args);
16
+ }
17
+
18
+ /**
19
+ * 空函数
20
+ */
21
+ function emptyFunc(): void {
22
+ }
23
+
24
+ /**
25
+ * 执行函数
26
+ * @param context
27
+ * @param func
28
+ * @param args
29
+ * @param doneCallback
30
+ */
31
+ function executeFunc(context: any, func: Function, args: any[], doneCallback: Function): any {
32
+ const value = func.apply(context, args);
33
+ // 如果不会返回promise
34
+ if (!value || !value.then || typeof value.then !== 'function') {
35
+ doneCallback();
36
+ return value;
37
+ }
38
+
39
+ return value.then((res: any) => {
40
+ doneCallback();
41
+ return res;
42
+ }).catch((err: any) => {
43
+ doneCallback();
44
+ return Promise.reject(err);
45
+ });
46
+ }
47
+
48
+ const maxDepth = 5;
49
+ const maxArrLen = 10;
50
+ const maxStrLen = 30;
51
+ const maxFieldLen = 20;
52
+
53
+ function isBasicsType(obj: any): { isBasics: boolean, value: any } {
54
+ const type = typeof obj;
55
+ if (obj === null || obj === undefined
56
+ || type === 'number' || type === 'boolean' || type === 'bigint') {
57
+ return { isBasics: true, value: obj };
58
+ }
59
+ if (type === 'string') {
60
+ const value = obj.substr(0, maxStrLen);
61
+ return { isBasics: true, value };
62
+ }
63
+ if (type === 'function' || type === 'symbol') {
64
+ return { isBasics: true, value: undefined };
65
+ }
66
+ return { isBasics: false, value: obj };
67
+ }
68
+
69
+ function isArrayType(obj: any): { isArray: boolean, value: any } {
70
+ if (Array.isArray(obj)) {
71
+ const arr = obj.slice(0, maxArrLen);
72
+ return { isArray: true, value: arr };
73
+ }
74
+ return { isArray: false, value: obj };
75
+ }
76
+
77
+ /**
78
+ * 深表克隆
79
+ * @param obj
80
+ * @param depth 当前克隆的深度
81
+ */
82
+ function deepClone(obj: any, depth: number): any {
83
+ if (depth >= maxDepth) {
84
+ return undefined;
85
+ }
86
+ const res1 = isBasicsType(obj);
87
+ if (res1.isBasics) {
88
+ return res1.value;
89
+ }
90
+ const res2 = isArrayType(obj);
91
+ if (res2.isArray) {
92
+ return res2.value.map((t: any) => deepClone(t, depth + 1));
93
+ }
94
+ const names = Object.getOwnPropertyNames(obj).slice(0, maxFieldLen);
95
+ const newObj = {};
96
+ for (const name of names) {
97
+ if (name === '__webviewId__') {
98
+ continue;
99
+ }
100
+ const value = obj[name];
101
+ const res1 = isBasicsType(value);
102
+ if (res1.isBasics) {
103
+ // @ts-ignore
104
+ newObj[name] = res1.value;
105
+ continue;
106
+ }
107
+ const res2 = isArrayType(value);
108
+ if (res2.isArray) {
109
+ // @ts-ignore
110
+ newObj[name] = res2.value.map((t: any) => deepClone(t, depth + 1));
111
+ continue;
112
+ }
113
+ // @ts-ignore
114
+ newObj[name] = deepClone(value, depth + 1);
115
+ }
116
+ return newObj;
117
+ }
118
+
119
+ /**
120
+ * 序列化data
121
+ * @param data
122
+ */
123
+ function serializeData(data: any): string {
124
+ const obj = deepClone(data, 0);
125
+ return JSON.stringify(obj);
126
+ }
127
+
128
+ export default {
129
+ reportData,
130
+ emptyFunc,
131
+ executeFunc,
132
+ serializeData,
133
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 负责页面和组件的全埋点
3
+ */
4
+
5
+ import proxyPage from './page';
6
+ import proxyComponent from './component';
7
+
8
+ let isInit = false;
9
+
10
+ function init(): void {
11
+ if (!isInit) {
12
+ isInit = true;
13
+ proxyPage.init();
14
+ proxyComponent.init();
15
+ }
16
+ }
17
+
18
+ export default {
19
+ init,
20
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * 负责页面的全埋点
3
+ */
4
+
5
+ import helper from './helper';
6
+
7
+ // 必须上报的生命周期函数
8
+ const mustLifeMethods = ['onLoad', 'onShow', 'onReady', 'onHide', 'onUnload'];
9
+ // 可能会有的生命周期函数
10
+ const maybeLifeMethods = [
11
+ 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage',
12
+ 'onShareTimeline', 'onAddToFavorites', 'onPageScroll',
13
+ ];
14
+ const lifeMethods = mustLifeMethods.concat(maybeLifeMethods);
15
+
16
+ let originalPage: any = null;
17
+
18
+ // 劫持Page的生命周期
19
+ function proxyLifeMethod(pageOptions: any, methodName: string): void {
20
+ if (!pageOptions[methodName] && !mustLifeMethods.includes(methodName)) {
21
+ return;
22
+ }
23
+ const original = pageOptions[methodName] || helper.emptyFunc;
24
+ // eslint-disable-next-line no-param-reassign
25
+ pageOptions[methodName] = function (...args: any[]): any {
26
+ let elapsed: number | '' = ''; // 页面曝光时间
27
+ let showCount: number | '' = ''; // 曝光次数
28
+ if (methodName === 'onShow') {
29
+ this.tmsPageShowCount = (this.tmsPageShowCount || 0) + 1;
30
+ this.tmsPageShowTime = Date.now();
31
+ showCount = this.tmsPageShowCount;
32
+ } else if (methodName === 'onHide' || methodName === 'onUnload') {
33
+ elapsed = Date.now() - this.tmsPageShowTime;
34
+ this.tmsPageShowTime = 0;
35
+ showCount = this.tmsPageShowCount;
36
+ }
37
+ // 执行原函数之后再发埋点
38
+ return helper.executeFunc(this, original, args, () => {
39
+ const data = helper.serializeData(this.data);
40
+ helper.reportData(`Page_${methodName}`, data, showCount, elapsed);
41
+ });
42
+ };
43
+ }
44
+
45
+ // 劫持其他事件
46
+ function proxyHandle(pageOptions: any, methodName: string): void {
47
+ const original = pageOptions[methodName] || helper.emptyFunc;
48
+ // eslint-disable-next-line no-param-reassign
49
+ pageOptions[methodName] = function (...args: any[]): any {
50
+ // 执行原函数之后再发埋点
51
+ return helper.executeFunc(this, original, args, () => {
52
+ const data = helper.serializeData(this.data);
53
+ helper.reportData(`Page_${methodName}`, data);
54
+ });
55
+ };
56
+ }
57
+
58
+ // 劫持Page
59
+ function init(): void {
60
+ // @ts-ignore
61
+ originalPage = Page;
62
+ // @ts-ignore
63
+ Page = function (options: any) {
64
+ // 只有tms的页面才需要上报
65
+ if (options.tmsAutoReport) {
66
+ for (const methodName of lifeMethods) {
67
+ proxyLifeMethod(options, methodName);
68
+ }
69
+ // tmsReportEvents是由工具在编译时分析出的要上报的事件列表
70
+ for (const methodName of options.tmsReportEvents) {
71
+ proxyHandle(options, methodName);
72
+ }
73
+ }
74
+ originalPage(options);
75
+ };
76
+ }
77
+
78
+ export default {
79
+ init,
80
+ };
@@ -73,7 +73,7 @@ function requestFail(batch: DataItem[][]): void {
73
73
  */
74
74
  function send(arr: DataItem[]): void {
75
75
  stopTimer();
76
- cacheArr.unshift(helper.encodeItems(arr)); // 如果队列中很多,排前面比较稳妥
76
+ cacheArr.unshift(arr); // 如果队列中很多,排前面比较稳妥
77
77
  batchSendData();
78
78
  }
79
79
 
@@ -82,7 +82,7 @@ function send(arr: DataItem[]): void {
82
82
  * @param arr
83
83
  */
84
84
  function queue(arr: DataItem[]): void {
85
- cacheArr.push(helper.encodeItems(arr));
85
+ cacheArr.push(arr);
86
86
  checkQueue(false);
87
87
  }
88
88
 
@@ -30,7 +30,7 @@ interface ISystemInfo {
30
30
  wxVersion: string,
31
31
  platform: string,
32
32
  SDKVersion: string,
33
- host: string,
33
+ host: object | '',
34
34
  }
35
35
 
36
36
  interface ILocation {