@tmsfe/tms-core 0.0.40 → 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.40",
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,14 +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();
15
14
  const { host } = helper.getSystemInfo();
15
+ const arr = new Array<string>(helper.dataArrLen);
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
@@ -50,7 +51,7 @@ function getBaseData(deviceData: IDeviceData): DataItem[] {
50
51
  arr[29] = helper.getAppScene();
51
52
  // 33: f33,系统信息
52
53
  arr[33] = helper.getSystemInfoString();
53
- // 36: f36,小程序启动时的参数
54
+ // 36: f36,小程序启动时的url和参数
54
55
  arr[36] = helper.getLaunchOptionsString();
55
56
  // --------------------------字段列表--------------------------
56
57
  return arr;
@@ -84,7 +85,7 @@ function formatData(data: IOldParams): Promise<DataItem[]> {
84
85
  * 格式化快速上报埋点数据,不依赖用户位置
85
86
  */
86
87
  function formatFastData(data: IOldParams): DataItem[] {
87
- return jointData(data, helper.defaultDevice);
88
+ return jointData(data, helper.getCacheDeviceData());
88
89
  }
89
90
 
90
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
 
@@ -95,8 +105,8 @@ let launchOptions: string | null = null;
95
105
  */
96
106
  function getLaunchOptionsString(): string {
97
107
  if (launchOptions === null) {
98
- const obj = syncApi.getLaunchOptionsSync() as any;
99
- launchOptions = obj.query ? JSON.stringify(obj.query) : '';
108
+ const obj = syncApi.getLaunchOptionsSync();
109
+ launchOptions = JSON.stringify(obj);
100
110
  }
101
111
  return launchOptions;
102
112
  }
@@ -106,7 +116,7 @@ function getLaunchOptionsString(): string {
106
116
  */
107
117
  function getLaunchFrom(): string {
108
118
  const obj = syncApi.getLaunchOptionsSync() as any;
109
- return obj.query?.from as string || '';
119
+ return obj.query?.from;
110
120
  }
111
121
 
112
122
  /**
@@ -141,46 +151,8 @@ function canReport(): boolean {
141
151
  */
142
152
  const dataArrLen = 41;
143
153
 
144
- const defaultDevice = {
145
- networkType: null,
146
- location: {
147
- province: null,
148
- cityName: null,
149
- },
150
- } as any as IDeviceData;
151
-
152
- let networkPromise: Promise<string> | null = null;
153
-
154
- function getNetworkType(): Promise<string> {
155
- if (networkPromise === null) {
156
- networkPromise = new Promise((resolve) => {
157
- wx.getNetworkType({
158
- success: (res: any) => resolve(res.networkType),
159
- fail: () => resolve('unknown'),
160
- });
161
- });
162
- }
163
- return networkPromise;
164
- }
165
-
166
- let locPromise: Promise<ILocation> | null = null;
167
-
168
- function getLocation(): Promise<ILocation> {
169
- if (locPromise === null) {
170
- locPromise = new Promise<ILocation>((resolve) => {
171
- const LOC = getTms().getLocationManager();
172
- LOC.getIpLocation()
173
- .then((res: any) => {
174
- const ad = res.ad_info;
175
- resolve({ province: ad.province, cityName: ad.city });
176
- })
177
- .catch(() => {
178
- resolve({ province: '', cityName: '' });
179
- });
180
- });
181
- }
182
- return locPromise;
183
- }
154
+ // 缓存设备等信息,方便FastReport使用
155
+ let deviceData: IDeviceData;
184
156
 
185
157
  let devicePromise: Promise<IDeviceData> | null = null;
186
158
 
@@ -190,15 +162,45 @@ let devicePromise: Promise<IDeviceData> | null = null;
190
162
  function getDeviceData(): Promise<IDeviceData> {
191
163
  if (devicePromise === null) {
192
164
  devicePromise = new Promise<IDeviceData>((resolve) => {
193
- Promise.all([getNetworkType(), getLocation()]).then((res) => {
194
- const [networkType, location] = res;
195
- 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
+ });
196
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());
189
+ });
190
+
191
+ Promise.all([networkPromise, locPromise]).then(() => resolve(deviceData));
197
192
  });
198
193
  }
199
194
  return devicePromise;
200
195
  }
201
196
 
197
+ /**
198
+ * 获取缓存的设备信息(网络状况,位置信息)
199
+ */
200
+ function getCacheDeviceData(): IDeviceData {
201
+ return deviceData;
202
+ }
203
+
202
204
  export default {
203
205
  init,
204
206
  getTms,
@@ -212,6 +214,6 @@ export default {
212
214
  getAppScene,
213
215
  canReport,
214
216
  dataArrLen,
215
- defaultDevice,
216
217
  getDeviceData,
218
+ getCacheDeviceData,
217
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
+ };