visual-buried-point-platform-h5 1.3.4

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.
@@ -0,0 +1,300 @@
1
+ /**
2
+ * "/api/v1/report/web",
3
+ * "livereload.js?snipver=1",
4
+ * "/sockjs-node/info",
5
+ */
6
+
7
+ type Ignore = {
8
+ ignoreErrors?: any[];
9
+ ignoreUrls?: any[];
10
+ ignoreApis?: any[];
11
+ };
12
+
13
+ type TBehavior = {
14
+ /**
15
+ * 取值可以是"debug", "info", "warn", "log", "error"
16
+ * ["log", "error"]
17
+ */
18
+ console: any[];
19
+ click: boolean;
20
+ };
21
+
22
+ type EnvironmentType = "dev" | "test" | "prod";
23
+
24
+ /** 初始化类型参数 */
25
+ interface IInitConfig {
26
+ /** 上报地址 */
27
+ reportUrl?: string;
28
+ /** 新的数据结构自定义上报 */
29
+ reportEventUrl?: string;
30
+ /** 新的数据结构PVUV上报 */
31
+ reportTrackUrl?: string;
32
+ /** 获取已埋点事件列表查询地址 */
33
+ eventListPath?: string;
34
+ /** 提交参数 */
35
+ token: string;
36
+ /** appkey */
37
+ appkey?: string;
38
+ /** 业务板块 */
39
+ busSegment?: string | number;
40
+ /** 业务模块 */
41
+ module?: string | number;
42
+ /** 环境 默认开发环境 `dev` */
43
+ environment: EnvironmentType;
44
+ /** app版本 */
45
+ appVersion?: string;
46
+ /** 脚本延迟上报时间 */
47
+ outtime?: number;
48
+ /** 开启单页面 */
49
+ enableSPA?: boolean;
50
+ /** 是否自动上报pv */
51
+ autoSendPv?: boolean;
52
+ /** 是否上报页面性能数据 */
53
+ isPage?: boolean;
54
+ /** 是否上报ajax性能数据 */
55
+ isAjax?: boolean;
56
+ /** 是否上报页面资源数据 */
57
+ isResource?: boolean;
58
+ /** 是否上报错误信息 */
59
+ isError?: boolean;
60
+ /** 是否录屏 */
61
+ isRecord?: boolean;
62
+ /** 是否上报行为 */
63
+ isBehavior?: boolean;
64
+ /** 直接监听接口名称 */
65
+ ignore: Ignore;
66
+ /** 监听用户行为 */
67
+ behavior: TBehavior;
68
+ /** 最长上报数据长度 */
69
+ maxLength?: number;
70
+ /** Web JS SDK 的 H5 页面,在嵌入到 App 后,H5 内的事件可以通过 App 进行发送 默认关闭 */
71
+ enableJavaScriptBridge: boolean;
72
+ /** 打通H5与APP通信后,安卓上报数据ID */
73
+ androidToken?: string | null | undefined;
74
+ /** 打通H5与APP通信后,Ios上报数据ID */
75
+ iosToken?: string | null | undefined;
76
+ /** 是否开启了页面浏览时长采集 */
77
+ enableTrackPageLeave?: boolean;
78
+ /** 是否开启了CSS HASH值 必传 */
79
+ isOpenCSSHash: boolean;
80
+ /** puv上报地址 */
81
+ reportPUVUrl?: string;
82
+ /** 元素配置表 */
83
+ metaConfigPath?: string;
84
+ /** 流量上报的 app: { name: '' } */
85
+ t_name?: string;
86
+ /** 流量上报的 app: { version: '' } */
87
+ t_version?: string;
88
+ }
89
+
90
+ interface ConfigParams {
91
+ domain: string;
92
+ token: string;
93
+ record: boolean;
94
+ }
95
+
96
+ interface Records {
97
+ type: string;
98
+ data: ReportData;
99
+ }
100
+
101
+ type ReportData =
102
+ | ErrorMsg
103
+ | ResourceMsg
104
+ | ApiMsg
105
+ | pvMsg
106
+ | healthMsg
107
+ | perfMsg
108
+ | behaviorMsg
109
+ | sumMsg
110
+ | avgMsg
111
+ | percentMsg
112
+ | msgMsg;
113
+ //
114
+ type MsgType =
115
+ | ""
116
+ | "error"
117
+ | "res"
118
+ | "api"
119
+ | "pv"
120
+ | "health"
121
+ | "perf"
122
+ | "behavior"
123
+ | "sum"
124
+ | "avg"
125
+ | "percent"
126
+ | "msg";
127
+
128
+ interface CommonMsg {
129
+ t: MsgType; // 类型
130
+ times?: number; // 次数
131
+ page: string; // 页面
132
+ v: string; // 版本
133
+ e: string; // 开发生产环境
134
+ token: string; // 项目id
135
+ timestamp: number; // 开始时间戳
136
+ screen_resolution: string; // 屏幕分辨率
137
+ view_resolution: string; // view 分辨率
138
+ sdk_version: string; // sdk版本
139
+ uid: string; // user id
140
+ sid: string; // session id
141
+ network: string; // 网络
142
+ language: string; // 语言
143
+ original_url: string; // 原始url
144
+ }
145
+
146
+ // pv上报
147
+ interface pvMsg extends CommonMsg {
148
+ dt: string; // document title
149
+ dl: string; // document location
150
+ dr: string; // 来源
151
+ dpr: number; // dpr
152
+ de?: string; // document 编码
153
+ }
154
+ interface ErrorMsg extends CommonMsg {
155
+ st: string; // sub type
156
+ msg: string; // 信息
157
+ cate?: string; // 类别
158
+ detail?: string; // 错误栈 或 出错标签
159
+ file?: string; // 出错文件
160
+ line?: number; // 行
161
+ col?: number; // 列
162
+ }
163
+
164
+ interface ResourceMsg extends CommonMsg {
165
+ dom: number; // 所有解析时间 domInteractive - responseEnd
166
+ load: number; // 所有资源加载完时间 loadEventStart- fetchStart
167
+ res: PerformanceEntry[];
168
+ }
169
+
170
+ interface ApiMsg extends CommonMsg {
171
+ url: string; // 接口
172
+ success: boolean; // 成功?
173
+ time: number; // 耗时
174
+ code: number; // 接口返回的code
175
+ msg: string; // 信息
176
+ // method:string
177
+ // params: string
178
+ // query: string
179
+ // body: string
180
+ // response:string
181
+ }
182
+
183
+ // 健康检查上报
184
+ interface healthMsg extends CommonMsg, Health {
185
+ healthy: number; // 健康? 0/1
186
+ stay: number; // 停留时间
187
+ }
188
+
189
+ interface Health {
190
+ errcount: number; // error次数
191
+ apisucc: number; // api成功次数
192
+ apifail: number; // api错误次数
193
+ }
194
+
195
+ // 页面性能上报
196
+ interface perfMsg extends CommonMsg {
197
+ dns: number; // dns时间
198
+ tcp: number; // tcp时间
199
+ ssl: number; // ssl时间
200
+ ttfb: number; // ResponseStart - RequestStart (首包时间,关注网络链路耗时)
201
+ trans: number; // 停留时间
202
+ dom: number; // dom解析时间
203
+ res: number; // 停留时间
204
+ firstbyte: number; // 首字节时间
205
+ fpt: number; // ResponseEnd - FetchStart (首次渲染时间 / 白屏时间)
206
+ tti: number; // DomInteractive - FetchStart (首次可交付时间)
207
+ ready: number; // DomContentLoadEventEnd - FetchStart (加载完成时间)
208
+ load: number; // LoadEventStart - FetchStart (页面完全加载时间)
209
+ bandwidth: number; // 估计的带宽 单位M/s
210
+ navtype: string; // nav方式 如reload
211
+ fmp: number; // 停留时间
212
+ }
213
+
214
+ // 行为上报
215
+ interface behaviorMsg extends CommonMsg {
216
+ behavior: Behavior;
217
+ }
218
+
219
+ // 手动上报 tag: string // 标签
220
+
221
+ type Behavior = navigationBehavior | consoleBehavior | eventBehavior;
222
+
223
+ interface navigationBehavior {
224
+ type: "navigation";
225
+ data: {
226
+ from: string;
227
+ to: string;
228
+ };
229
+ }
230
+
231
+ interface consoleBehavior {
232
+ type: "console";
233
+ data: {
234
+ level: string;
235
+ message: string;
236
+ };
237
+ }
238
+
239
+ interface eventBehavior {
240
+ type: string;
241
+ data: {
242
+ path: string;
243
+ message: string;
244
+ };
245
+ }
246
+
247
+ interface sumMsg extends CommonMsg {
248
+ group: string;
249
+ key: string;
250
+ val: number;
251
+ }
252
+
253
+ interface avgMsg extends CommonMsg {
254
+ group: string;
255
+ key: string;
256
+ val: number;
257
+ }
258
+
259
+ interface percentMsg extends CommonMsg {
260
+ group: string;
261
+ key: string;
262
+ subkey: string;
263
+ val: number;
264
+ }
265
+
266
+ interface msgMsg extends CommonMsg {
267
+ group: string;
268
+ msg: string;
269
+ }
270
+
271
+ // 内嵌H5上报页面信息数据类型
272
+
273
+ interface IPageData {
274
+ cssData: string;
275
+ elementData: string;
276
+ pageName: string;
277
+ pageTitle: string;
278
+ url?: string;
279
+ origin?: string;
280
+ platform: string;
281
+ }
282
+
283
+ interface IReportData {
284
+ /** 页面内容 */
285
+ gzip_payload: string;
286
+ /** 当前页面完整路径 */
287
+ urlPath: string;
288
+ /** 是否需要终端查询静态资源文件 */
289
+ need?: boolean;
290
+ }
291
+
292
+ //自定义数据的类型
293
+ export interface ICustomData {
294
+ /** 事件ID */
295
+ $event_id: string;
296
+ /** 扩展参数 */
297
+ $extend_param?: any;
298
+ /** 浏览URL */
299
+ $url_path?: string;
300
+ }
@@ -0,0 +1,30 @@
1
+ // 复制缓存数据
2
+ export function deepCopy(target) {
3
+ if (typeof target === "object") {
4
+ const result = Array.isArray(target) ? [] : {};
5
+ for (const key in target) {
6
+ if (typeof target[key] == "object") {
7
+ result[key] = deepCopy(target[key]);
8
+ } else {
9
+ result[key] = target[key];
10
+ }
11
+ }
12
+ return result;
13
+ }
14
+ return target;
15
+ }
16
+
17
+ // 数据缓存
18
+ const cache = [];
19
+
20
+ export function getCache() {
21
+ return deepCopy(cache);
22
+ }
23
+
24
+ export function addCache(data) {
25
+ cache.push(data);
26
+ }
27
+
28
+ export function clearCache() {
29
+ cache.length = 0;
30
+ }
@@ -0,0 +1,30 @@
1
+ // 复制缓存数据
2
+ export function deepCopy(target) {
3
+ if (typeof target === "object") {
4
+ const result = Array.isArray(target) ? [] : {};
5
+ for (const key in target) {
6
+ if (typeof target[key] == "object") {
7
+ result[key] = deepCopy(target[key]);
8
+ } else {
9
+ result[key] = target[key];
10
+ }
11
+ }
12
+ return result;
13
+ }
14
+ return target;
15
+ }
16
+
17
+ // 数据缓存
18
+ const cacheCu = [];
19
+
20
+ export function getCacheCu() {
21
+ return deepCopy(cacheCu);
22
+ }
23
+
24
+ export function addCacheCu(data) {
25
+ cacheCu.push(data);
26
+ }
27
+
28
+ export function clearCacheCu() {
29
+ cacheCu.length = 0;
30
+ }
@@ -0,0 +1,30 @@
1
+ // 复制缓存数据
2
+ export function deepCopyPuv(target) {
3
+ if (typeof target === "object") {
4
+ const result = Array.isArray(target) ? [] : {};
5
+ for (const key in target) {
6
+ if (typeof target[key] == "object") {
7
+ result[key] = deepCopyPuv(target[key]);
8
+ } else {
9
+ result[key] = target[key];
10
+ }
11
+ }
12
+ return result;
13
+ }
14
+ return target;
15
+ }
16
+
17
+ // 数据缓存
18
+ const cache = [];
19
+
20
+ export function getCachePuv() {
21
+ return deepCopyPuv(cache);
22
+ }
23
+
24
+ export function addCachePuv(data) {
25
+ cache.push(data);
26
+ }
27
+
28
+ export function clearCachePuv() {
29
+ cache.length = 0;
30
+ }
@@ -0,0 +1,236 @@
1
+ import pako from "pako";
2
+ import { Base64 } from "js-base64";
3
+ import MD5 from "md5";
4
+ import { GlobalVal } from "../config/global";
5
+ import { getLocationHref, isPlatform } from "./tools";
6
+ import { getPathSelector, getXPath } from "./getXPath";
7
+ import html2canvas from "html2canvas";
8
+
9
+ /** 判断元素是否在可视区 */
10
+ function isInViewPort(element) {
11
+ // const viewWidth = window.innerWidth || document.documentElement.clientWidth;
12
+ // const viewHeight =
13
+ // window.innerHeight || document.documentElement.clientHeight;
14
+ // const { top, right, bottom, left } = element.getBoundingClientRect();
15
+
16
+ // if (bottom <= 0 || top > viewHeight) {
17
+ // // y 轴方向
18
+ // return false;
19
+ // }
20
+ // if (right <= 0 || left > viewWidth) {
21
+ // // x 轴方向
22
+ // return false;
23
+ // }
24
+ return true;
25
+ }
26
+
27
+ function getChildren(data, arr, level) {
28
+ for (let i = 0; i < data.childNodes.length; i++) {
29
+ const element = data.childNodes[i];
30
+ if (
31
+ element.nodeType !== Node.TEXT_NODE &&
32
+ element.nodeType !== Node.COMMENT_NODE
33
+ ) {
34
+ /** 判断元素是否在可视区 */
35
+ if (isInViewPort(element)) {
36
+ const path = getXPath(element);
37
+ const selector = getPathSelector(element);
38
+ let text = "";
39
+ if (element.innerText && element.innerText.length > 30) {
40
+ text = element.innerText.substring(0, 30);
41
+ } else {
42
+ text = element.innerText;
43
+ }
44
+ let elObj = {
45
+ name: element.tagName.toLocaleLowerCase(),
46
+ className: element.getAttribute("class"),
47
+ properties: {
48
+ width: element.offsetWidth,
49
+ height: element.offsetHeight,
50
+ element_path: path,
51
+ element_selector: selector,
52
+ level: ++level,
53
+ x: element.getBoundingClientRect().left,
54
+ y: element.getBoundingClientRect().top,
55
+ element_content: text,
56
+ },
57
+ };
58
+ arr.push(elObj);
59
+ if (element.childNodes.length) {
60
+ getChildren(element, arr, level);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return arr;
66
+ }
67
+ function getChild(data, result = []) {
68
+ for (let i = 0; i < data.childNodes.length; i++) {
69
+ const element = data.childNodes[i];
70
+ if (
71
+ element.nodeType !== Node.TEXT_NODE &&
72
+ element.nodeType !== Node.COMMENT_NODE
73
+ ) {
74
+ /** 判断元素是否在可视区 */
75
+ if (isInViewPort(element)) {
76
+ const path = getXPath(element);
77
+ const selector = getPathSelector(element);
78
+ let text = "";
79
+ if (element.innerText && element.innerText.length > 30) {
80
+ text = element.innerText.substring(0, 30);
81
+ } else {
82
+ text = element.innerText;
83
+ }
84
+ let elObj = {
85
+ name: element.tagName.toLocaleLowerCase(),
86
+ className: element.getAttribute("class"),
87
+ properties: {
88
+ width: element.offsetWidth,
89
+ height: element.offsetHeight,
90
+ element_path: path,
91
+ element_selector: selector,
92
+ level: 1,
93
+ x: element.getBoundingClientRect().left,
94
+ y: element.getBoundingClientRect().top,
95
+ element_content: text,
96
+ },
97
+ };
98
+
99
+ result.push(elObj);
100
+ if (element.childNodes.length) {
101
+ getChildren(element, result, 1);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ return result;
107
+ }
108
+
109
+ export function cloneDom() {
110
+ setTimeout(() => {
111
+ if (!GlobalVal.enableJavaScriptBridgeCircle) {
112
+ return false;
113
+ }
114
+ const originPath = window.location.origin;
115
+
116
+ const documentStyle = document.querySelector("head");
117
+ const childNodeDom = documentStyle.childNodes;
118
+ let cssTxt = "",
119
+ linkTxt = "";
120
+ for (let i = 0; i < childNodeDom.length; i++) {
121
+ const childNode = childNodeDom[i];
122
+ if (childNode.nodeName === "STYLE") {
123
+ cssTxt += (childNode as any).outerHTML;
124
+ }
125
+ if (childNode.nodeName === "LINK") {
126
+ const linkHref = (childNode as any).href;
127
+ if (linkHref.startsWith("http://") || linkHref.startsWith("https://")) {
128
+ (childNode as any).setAttribute("href", linkHref);
129
+ linkTxt += (childNode as any).outerHTML;
130
+ } else {
131
+ linkTxt += "";
132
+ }
133
+ }
134
+ }
135
+ const target =
136
+ document.querySelector("#app") || document.querySelector("#root");
137
+ const cloneNode = target.cloneNode(true);
138
+ const elementTxt = (cloneNode as any).outerHTML;
139
+ let locationPath = getLocationHref();
140
+
141
+ const params: IPageData = {
142
+ cssData: `${linkTxt}${cssTxt}`,
143
+ elementData: elementTxt,
144
+ pageName: GlobalVal.page,
145
+ pageTitle: document.title,
146
+ url: locationPath,
147
+ origin: location.origin,
148
+ platform: "H5",
149
+ };
150
+ let reportData: IReportData | Record<string, any> = {};
151
+ // 非本地资源不通知APP端获取静态资源
152
+ if (originPath.startsWith("http://") || originPath.startsWith("https://")) {
153
+ // 数据加密传输
154
+ const paramsStr = JSON.stringify(params);
155
+ const outputData = pako.gzip(paramsStr, { to: "string" });
156
+ const base64Str = Base64.btoa(outputData);
157
+ // MD5 加密校验
158
+ const md5Str: string = MD5(base64Str);
159
+ reportData = {
160
+ gzip_payload: base64Str,
161
+ urlPath: locationPath,
162
+ // gzip_payload_MD5: md5Str,
163
+ } as IReportData;
164
+ } else {
165
+ // 本地资源通知APP端获取静态资源 数据不加密
166
+ reportData = {
167
+ gzip_payload: JSON.stringify(params),
168
+ urlPath: location.href.split("?")[0],
169
+ need: true,
170
+ } as IReportData;
171
+ }
172
+ const platform = isPlatform();
173
+ if (platform && platform === "android") {
174
+ (window as any).dsy_visual.receive_web_track_nodes(
175
+ JSON.stringify(reportData)
176
+ );
177
+ }
178
+ if (platform && platform === "ios") {
179
+ (window as any).webkit.messageHandlers.visualBuriediOS.postMessage(
180
+ JSON.stringify(reportData)
181
+ );
182
+ }
183
+ }, 5000);
184
+ }
185
+
186
+ /** 内嵌web项目抓取节点通过APP上传 */
187
+ export function getDocumentElement() {
188
+ if (!GlobalVal.enableJavaScriptBridgeCircle) {
189
+ return;
190
+ }
191
+ const bodyEl = document.querySelector("body");
192
+ const reportDom = getChild(bodyEl);
193
+ let locationPath = getLocationHref();
194
+ // 可视区截屏
195
+ html2canvas(bodyEl)
196
+ .then((canvas) => {
197
+ return canvas.toDataURL("image/png");
198
+ })
199
+ .then((res) => {
200
+ const params = {
201
+ image_path: res,
202
+ image_hash: res.substring(res.length - 24, res.length),
203
+ elementDom: reportDom,
204
+ rootW: document.documentElement.clientWidth,
205
+ rootH: document.documentElement.clientHeight,
206
+ pageName: GlobalVal.page,
207
+ pageTitle: document.title,
208
+ url: locationPath,
209
+ origin: location.origin,
210
+ platform: "H5",
211
+ };
212
+
213
+ // 数据加密传输
214
+ const paramsStr = JSON.stringify(params);
215
+ const outputData = pako.gzip(paramsStr, { to: "string" });
216
+ const base64Str = Base64.btoa(outputData);
217
+ const reportData = {
218
+ gzip_payload: base64Str,
219
+ urlPath: location.href.split("?")[0], // 防止IOS存取错误,仅对URL做参数截取
220
+ } as IReportData;
221
+ const platform = isPlatform();
222
+ if (platform && platform === "android") {
223
+ (window as any)?.dsy_visual?.receive_web_track_nodes(
224
+ JSON.stringify(reportData)
225
+ );
226
+ }
227
+ if (platform && platform === "ios") {
228
+ (window as any)?.webkit?.messageHandlers?.visualBuriediOS?.postMessage(
229
+ JSON.stringify(reportData)
230
+ );
231
+ }
232
+ })
233
+ .catch((err) => {
234
+ console.error(err);
235
+ });
236
+ }