monitor-track 1.10.0
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/CHANGELOG.md +31 -0
- package/README.md +101 -0
- package/dist/CHANGELOG.md +21 -0
- package/dist/README.md +101 -0
- package/dist/cjs/config/global.d.ts +17 -0
- package/dist/cjs/config/index.d.ts +33 -0
- package/dist/cjs/constant.d.ts +2 -0
- package/dist/cjs/handlers/error.d.ts +23 -0
- package/dist/cjs/handlers/index.d.ts +3 -0
- package/dist/cjs/handlers/pv.d.ts +24 -0
- package/dist/cjs/handlers/user-activity.d.ts +9 -0
- package/dist/cjs/index.d.ts +30 -0
- package/dist/cjs/index.js +1485 -0
- package/dist/cjs/main.d.ts +1 -0
- package/dist/cjs/reporter.d.ts +198 -0
- package/dist/cjs/types/config.d.ts +59 -0
- package/dist/cjs/types/global.d.ts +70 -0
- package/dist/cjs/types/index.d.ts +189 -0
- package/dist/cjs/utils/index.d.ts +45 -0
- package/dist/esm/_virtual/_tslib.js +26 -0
- package/dist/esm/config/global.d.ts +17 -0
- package/dist/esm/config/global.js +271 -0
- package/dist/esm/config/index.d.ts +33 -0
- package/dist/esm/config/index.js +32 -0
- package/dist/esm/constant.d.ts +2 -0
- package/dist/esm/constant.js +4 -0
- package/dist/esm/handlers/error.d.ts +23 -0
- package/dist/esm/handlers/error.js +181 -0
- package/dist/esm/handlers/index.d.ts +3 -0
- package/dist/esm/handlers/pv.d.ts +24 -0
- package/dist/esm/handlers/pv.js +99 -0
- package/dist/esm/handlers/user-activity.d.ts +9 -0
- package/dist/esm/handlers/user-activity.js +66 -0
- package/dist/esm/index.d.ts +30 -0
- package/dist/esm/index.js +115 -0
- package/dist/esm/main.d.ts +1 -0
- package/dist/esm/package.json.js +3 -0
- package/dist/esm/reporter.d.ts +198 -0
- package/dist/esm/reporter.js +359 -0
- package/dist/esm/types/config.d.ts +59 -0
- package/dist/esm/types/global.d.ts +70 -0
- package/dist/esm/types/index.d.ts +189 -0
- package/dist/esm/utils/index.d.ts +45 -0
- package/dist/esm/utils/index.js +354 -0
- package/dist/index.js +1534 -0
- package/dist/package.json +41 -0
- package/package.json +42 -0
- package/rollup.config.js +66 -0
- package/scripts/cp.js +11 -0
- package/src/config/global.ts +309 -0
- package/src/config/index.ts +53 -0
- package/src/constant.ts +2 -0
- package/src/handlers/error.ts +187 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/pv.ts +124 -0
- package/src/handlers/user-activity.ts +69 -0
- package/src/index.ts +127 -0
- package/src/main.ts +8 -0
- package/src/reporter.ts +372 -0
- package/src/types/config.ts +58 -0
- package/src/types/global.ts +73 -0
- package/src/types/index.ts +227 -0
- package/src/typing.d.ts +15 -0
- package/src/utils/index.ts +371 -0
- package/tsconfig.esm.json +14 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
|
+
import { report } from '../reporter.js';
|
|
3
|
+
import { getSessionId, getNavigator, getViewport, getUid } from '../utils/index.js';
|
|
4
|
+
import { setRouteStack, setPVTime } from '../handlers/pv.js';
|
|
5
|
+
import { Config } from './index.js';
|
|
6
|
+
import { version } from '../package.json.js';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* @Author: Mark.Zhang
|
|
10
|
+
* @Date: 2020-10-26 15:40:16
|
|
11
|
+
* @Description 全局配置项
|
|
12
|
+
*/
|
|
13
|
+
// 上报数据
|
|
14
|
+
let Report = {
|
|
15
|
+
uuid: '',
|
|
16
|
+
projectID: '',
|
|
17
|
+
host: '',
|
|
18
|
+
originPage: '',
|
|
19
|
+
page: '',
|
|
20
|
+
time: 0,
|
|
21
|
+
browserName: '',
|
|
22
|
+
browserVersion: '',
|
|
23
|
+
engineName: '',
|
|
24
|
+
engineVersion: '',
|
|
25
|
+
language: '',
|
|
26
|
+
navigatorVendor: '',
|
|
27
|
+
connectionType: '2g',
|
|
28
|
+
osName: '',
|
|
29
|
+
osVersion: '',
|
|
30
|
+
type: 'init',
|
|
31
|
+
viewport: '',
|
|
32
|
+
screen: '',
|
|
33
|
+
version: '',
|
|
34
|
+
charset: '',
|
|
35
|
+
pageTitle: '',
|
|
36
|
+
referrer: '',
|
|
37
|
+
pv: null,
|
|
38
|
+
ua: null,
|
|
39
|
+
error: null,
|
|
40
|
+
dpr: 1,
|
|
41
|
+
perf: null,
|
|
42
|
+
vD: undefined,
|
|
43
|
+
manualReport: undefined,
|
|
44
|
+
lifeCycleId: v4(),
|
|
45
|
+
sessionId: getSessionId(),
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* @description 初始化上报数据并上报
|
|
49
|
+
*/
|
|
50
|
+
function initReport() {
|
|
51
|
+
if (document.readyState === 'complete') {
|
|
52
|
+
initReportFunc();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// 注意:这里不要使用window.onload,因为一个项目window.onload只能用一次,如果这里用了就会影响宿主项目的功能
|
|
56
|
+
window.addEventListener('load', initReportFunc);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function initReportFunc() {
|
|
60
|
+
setRouteStack([location.href, location.href]);
|
|
61
|
+
setPVTime();
|
|
62
|
+
Report = Object.assign(Object.assign({}, getReport()), {
|
|
63
|
+
page: location.href,
|
|
64
|
+
originPage: location.href,
|
|
65
|
+
type: 'init',
|
|
66
|
+
perf: performance,
|
|
67
|
+
sessionId: getSessionId(),
|
|
68
|
+
lifeCycleId: v4(),
|
|
69
|
+
});
|
|
70
|
+
report(Report);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @description 设置Report的值
|
|
74
|
+
* @param key Report key
|
|
75
|
+
* @param value Report 值
|
|
76
|
+
*/
|
|
77
|
+
function setReportValue(key, value) {
|
|
78
|
+
if (Object.prototype.hasOwnProperty.call(Report, key)) {
|
|
79
|
+
if (['pv', 'ua', 'error', 'request'].includes(key)) {
|
|
80
|
+
Report = Object.assign(Object.assign({}, Report), {
|
|
81
|
+
pv: null,
|
|
82
|
+
ua: null,
|
|
83
|
+
error: null,
|
|
84
|
+
req: null,
|
|
85
|
+
manualReport: undefined,
|
|
86
|
+
perf: null,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
Report[key] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* @description 获取上报数据
|
|
94
|
+
*/
|
|
95
|
+
function getReport() {
|
|
96
|
+
const nav = getNavigator();
|
|
97
|
+
const viewport = getViewport();
|
|
98
|
+
return Object.assign(Object.assign(Object.assign({}, Report), nav), {
|
|
99
|
+
version,
|
|
100
|
+
projectID: Config.projectID,
|
|
101
|
+
host: location.host,
|
|
102
|
+
uuid: getUid(),
|
|
103
|
+
viewport,
|
|
104
|
+
screen: `${screen.width} x ${screen.height}`,
|
|
105
|
+
pageTitle: document.title,
|
|
106
|
+
referrer: document.referrer,
|
|
107
|
+
charset: document.charset,
|
|
108
|
+
customPayload: Config.customPayload,
|
|
109
|
+
dpr: window.devicePixelRatio,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function ajaxEventTrigger(event) {
|
|
113
|
+
const ajaxEvent = new CustomEvent(event, {
|
|
114
|
+
detail: this,
|
|
115
|
+
});
|
|
116
|
+
window.dispatchEvent(ajaxEvent);
|
|
117
|
+
}
|
|
118
|
+
const OldXHR = window.XMLHttpRequest;
|
|
119
|
+
function newXHR() {
|
|
120
|
+
const realXHR = new OldXHR();
|
|
121
|
+
realXHR.addEventListener('loadstart', function () {
|
|
122
|
+
ajaxEventTrigger.call(this, 'ajaxLoadStart');
|
|
123
|
+
}, false);
|
|
124
|
+
realXHR.addEventListener('loadend', function () {
|
|
125
|
+
ajaxEventTrigger.call(this, 'ajaxLoadEnd');
|
|
126
|
+
}, false);
|
|
127
|
+
// 此处的捕获的异常会连日志接口也一起捕获,如果日志上报接口异常了,就会导致死循环了。
|
|
128
|
+
realXHR.onerror = function (e) {
|
|
129
|
+
// eslint-disable-next-line no-console
|
|
130
|
+
console.warn('realXHR.onerror, e', e);
|
|
131
|
+
};
|
|
132
|
+
return realXHR;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 页面接口请求监控
|
|
136
|
+
*/
|
|
137
|
+
const tempUrlInfo = {};
|
|
138
|
+
const recordXMLHttpRequestLog = (XMLHttpRequestTimeout) => {
|
|
139
|
+
XMLHttpRequestTimeout = typeof XMLHttpRequestTimeout === 'number' ? XMLHttpRequestTimeout : 1000;
|
|
140
|
+
const timeRecordArray = [];
|
|
141
|
+
window.__XMLHttpRequest__ = window.XMLHttpRequest;
|
|
142
|
+
window.XMLHttpRequest = newXHR;
|
|
143
|
+
window.addEventListener('ajaxLoadStart', function (e) {
|
|
144
|
+
const tempObj = {
|
|
145
|
+
timeStamp: new Date().getTime(),
|
|
146
|
+
event: e,
|
|
147
|
+
};
|
|
148
|
+
timeRecordArray.push(tempObj);
|
|
149
|
+
});
|
|
150
|
+
window.addEventListener('ajaxLoadEnd', function () {
|
|
151
|
+
const timeRecordArrayCopy = [].concat(timeRecordArray);
|
|
152
|
+
for (let i = 0; i < timeRecordArrayCopy.length; i++) {
|
|
153
|
+
if (timeRecordArrayCopy[i].event.detail && timeRecordArrayCopy[i].event.detail.status > 0) {
|
|
154
|
+
const currentTime = new Date().getTime();
|
|
155
|
+
const { responseURL, status, statusText, timeStamp } = timeRecordArrayCopy[i].event.detail;
|
|
156
|
+
const previousTime = timeStamp || timeRecordArrayCopy[i].timeStamp;
|
|
157
|
+
const loadTime = currentTime - previousTime;
|
|
158
|
+
const request = {
|
|
159
|
+
requestType: 'xhr',
|
|
160
|
+
responseURL,
|
|
161
|
+
status,
|
|
162
|
+
loadTime,
|
|
163
|
+
statusText,
|
|
164
|
+
reason: '',
|
|
165
|
+
detail: '',
|
|
166
|
+
};
|
|
167
|
+
if (loadTime && loadTime > XMLHttpRequestTimeout) {
|
|
168
|
+
request.reason = 'slow';
|
|
169
|
+
request.detail = `request is too slow, XMLHttpRequestTimeout: ${XMLHttpRequestTimeout}`;
|
|
170
|
+
}
|
|
171
|
+
else if (status && status >= 300 && statusText && statusText.toLowerCase() !== 'ok') {
|
|
172
|
+
request.reason = 'failed';
|
|
173
|
+
request.detail = `request is failed, status: ${status}, statusText: ${statusText}`;
|
|
174
|
+
}
|
|
175
|
+
else ;
|
|
176
|
+
if (request.reason) {
|
|
177
|
+
if (!tempUrlInfo[responseURL]) {
|
|
178
|
+
tempUrlInfo[responseURL] = true;
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
delete tempUrlInfo[responseURL];
|
|
181
|
+
}, 10);
|
|
182
|
+
setReportValue('error', null);
|
|
183
|
+
report(Object.assign(Object.assign({}, getReport()), {
|
|
184
|
+
page: location.href,
|
|
185
|
+
originPage: location.href,
|
|
186
|
+
type: 'request',
|
|
187
|
+
req: request,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// 当前请求成功后就在数组中移除掉
|
|
193
|
+
timeRecordArray.splice(i, 1);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const hackFetch = (XMLHttpRequestTimeout) => {
|
|
198
|
+
XMLHttpRequestTimeout = typeof XMLHttpRequestTimeout === 'number' ? XMLHttpRequestTimeout : 1000;
|
|
199
|
+
if (typeof window.fetch === 'function') {
|
|
200
|
+
const __fetch__ = window.fetch;
|
|
201
|
+
window.__fetch__ = __fetch__;
|
|
202
|
+
window.fetch = function (t, ...args) {
|
|
203
|
+
const begin = Date.now();
|
|
204
|
+
//禁用数组的扩展运算符,否则portal的生产环境会报Uncaught TypeError: Object(...) is not a function,
|
|
205
|
+
//编译后的__spreadArray函数有问题,而这个函数来自于tslib.具体报错原因不知.
|
|
206
|
+
//其他平台使用数组的扩展运算符没有问题,比如前端监控平台的管理界面
|
|
207
|
+
const params = [].concat(t).concat(args);
|
|
208
|
+
return __fetch__
|
|
209
|
+
.apply(window, params)
|
|
210
|
+
.then(function (res) {
|
|
211
|
+
const response = res.clone();
|
|
212
|
+
const headers = response.headers;
|
|
213
|
+
if (headers && typeof headers.get === 'function') {
|
|
214
|
+
const ct = headers.get('content-type');
|
|
215
|
+
if (ct && !/(text)|(json)/.test(ct)) {
|
|
216
|
+
return res;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const loadTime = Date.now() - begin;
|
|
220
|
+
response
|
|
221
|
+
.text()
|
|
222
|
+
.then(function (result) {
|
|
223
|
+
const { url, status, statusText, ok } = response;
|
|
224
|
+
const request = {
|
|
225
|
+
requestType: 'fetch',
|
|
226
|
+
responseURL: url,
|
|
227
|
+
status,
|
|
228
|
+
loadTime,
|
|
229
|
+
statusText,
|
|
230
|
+
reason: '',
|
|
231
|
+
detail: '',
|
|
232
|
+
};
|
|
233
|
+
if (!ok || status >= 300) {
|
|
234
|
+
request.reason = 'failed';
|
|
235
|
+
request.detail = `request is failed, status: ${status}, statusText: ${statusText}`;
|
|
236
|
+
}
|
|
237
|
+
else if (loadTime > XMLHttpRequestTimeout) {
|
|
238
|
+
request.reason = 'slow';
|
|
239
|
+
request.detail = `request is too slow, XMLHttpRequestTimeout: ${XMLHttpRequestTimeout}, result: ${result === null || result === void 0 ? void 0 : result.slice(0, 500)}`;
|
|
240
|
+
}
|
|
241
|
+
if (request.reason) {
|
|
242
|
+
if (!tempUrlInfo[url]) {
|
|
243
|
+
tempUrlInfo[url] = true;
|
|
244
|
+
setTimeout(() => {
|
|
245
|
+
delete tempUrlInfo[url];
|
|
246
|
+
}, 10);
|
|
247
|
+
setReportValue('error', null);
|
|
248
|
+
report(Object.assign(Object.assign({}, getReport()), {
|
|
249
|
+
page: location.href,
|
|
250
|
+
originPage: location.href,
|
|
251
|
+
type: 'request',
|
|
252
|
+
req: request,
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.catch((err) => {
|
|
258
|
+
// eslint-disable-next-line no-console
|
|
259
|
+
console.log('hackFetch response.text() err', err);
|
|
260
|
+
});
|
|
261
|
+
return res;
|
|
262
|
+
})
|
|
263
|
+
.catch((err) => {
|
|
264
|
+
// eslint-disable-next-line no-console
|
|
265
|
+
console.log('hackFetch err', err);
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export { getReport, hackFetch, initReport, recordXMLHttpRequestLog, setReportValue };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IConfig } from '../types/config';
|
|
2
|
+
/** */
|
|
3
|
+
/**
|
|
4
|
+
* @description config配置项默认值
|
|
5
|
+
*/
|
|
6
|
+
export declare const Config: IConfig;
|
|
7
|
+
/**
|
|
8
|
+
* @description 设置config配置项
|
|
9
|
+
* @param config 配置项
|
|
10
|
+
*/
|
|
11
|
+
export declare function setConfig(config: IConfig): void;
|
|
12
|
+
/**
|
|
13
|
+
* @description 设置config配置项
|
|
14
|
+
* @param config 配置项
|
|
15
|
+
*/
|
|
16
|
+
export declare function setConfigValue<T extends IConfig, K extends keyof T>(key: K, value: T[K]): void;
|
|
17
|
+
/**
|
|
18
|
+
* @description 获取通过key值获取配置项value
|
|
19
|
+
* @param key
|
|
20
|
+
* @returns value的值
|
|
21
|
+
*/
|
|
22
|
+
export declare function getConfigValue(key: keyof IConfig): string | number | boolean | {
|
|
23
|
+
urls?: string[] | undefined;
|
|
24
|
+
errors?: (string | {
|
|
25
|
+
regExp: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* @description 设置config配置项
|
|
28
|
+
* @param config 配置项
|
|
29
|
+
*/
|
|
30
|
+
input: string;
|
|
31
|
+
})[] | undefined;
|
|
32
|
+
apis?: string[] | undefined;
|
|
33
|
+
} | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Mark.Zhang
|
|
3
|
+
* @Description 配置项相关参数及方法
|
|
4
|
+
*/
|
|
5
|
+
/** */
|
|
6
|
+
/**
|
|
7
|
+
* @description config配置项默认值
|
|
8
|
+
*/
|
|
9
|
+
const Config = {
|
|
10
|
+
reportUrl: '',
|
|
11
|
+
projectID: '',
|
|
12
|
+
maxLength: 1000,
|
|
13
|
+
spa: false,
|
|
14
|
+
hash: false,
|
|
15
|
+
enableBehavior: true,
|
|
16
|
+
enableError: true,
|
|
17
|
+
enableVisualTrack: false,
|
|
18
|
+
ignore: {
|
|
19
|
+
urls: [],
|
|
20
|
+
errors: [],
|
|
21
|
+
apis: [],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* @description 设置config配置项
|
|
26
|
+
* @param config 配置项
|
|
27
|
+
*/
|
|
28
|
+
function setConfig(config) {
|
|
29
|
+
Object.assign(Config, config);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { Config, setConfig };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 错误事件触发后的操作
|
|
3
|
+
*/
|
|
4
|
+
export declare function handleError(event: ErrorEvent | Event | PromiseRejectionEvent): void;
|
|
5
|
+
/**
|
|
6
|
+
* @description 设置错误信息
|
|
7
|
+
*/
|
|
8
|
+
export declare function setCaughtError(event: ErrorEvent): void;
|
|
9
|
+
/**
|
|
10
|
+
* @description 设置异步错误信息
|
|
11
|
+
*/
|
|
12
|
+
export declare function setPromiseError(event: PromiseRejectionEvent): void;
|
|
13
|
+
/**
|
|
14
|
+
* @description 设置资源错误信息
|
|
15
|
+
*/
|
|
16
|
+
export declare function setResourceError(event: Event): void;
|
|
17
|
+
/**
|
|
18
|
+
* 忽略的error
|
|
19
|
+
* @param errorMsg 错误信息
|
|
20
|
+
*/
|
|
21
|
+
export declare function ignoreError(errorMsg: string): boolean;
|
|
22
|
+
export declare function getUserEvents(notUseCache: boolean): string | undefined;
|
|
23
|
+
export declare function getFullScreenShoot(filename: string | undefined): Promise<File>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as rrweb from 'rrweb';
|
|
2
|
+
import ErrorStackParser from 'error-stack-parser';
|
|
3
|
+
import html2canvas from 'html2canvas';
|
|
4
|
+
import { v4 } from 'uuid';
|
|
5
|
+
import { report } from '../reporter.js';
|
|
6
|
+
import { setReportValue, getReport } from '../config/global.js';
|
|
7
|
+
import { Config } from '../config/index.js';
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* @Author: Mark.Zhang
|
|
11
|
+
* @Date: 2020-10-26 11:10:04
|
|
12
|
+
* @Description 资源加载错误及js错误相关的处理方法
|
|
13
|
+
*/
|
|
14
|
+
const eventsMatrix = [[]];
|
|
15
|
+
rrweb.record({
|
|
16
|
+
emit(event, isCheckout) {
|
|
17
|
+
// isCheckout 是一个标识,告诉你重新制作了快照
|
|
18
|
+
if (isCheckout) {
|
|
19
|
+
eventsMatrix.push([]);
|
|
20
|
+
}
|
|
21
|
+
if (eventsMatrix.length > 2) {
|
|
22
|
+
eventsMatrix.shift();
|
|
23
|
+
}
|
|
24
|
+
const lastEvents = eventsMatrix[eventsMatrix.length - 1];
|
|
25
|
+
lastEvents.push(event);
|
|
26
|
+
},
|
|
27
|
+
// 每30秒重新制作快照
|
|
28
|
+
checkoutEveryNms: 30 * 1000,
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* @description 错误事件触发后的操作
|
|
32
|
+
*/
|
|
33
|
+
function handleError(event) {
|
|
34
|
+
setReportValue('type', 'error');
|
|
35
|
+
if (event.type === 'error') {
|
|
36
|
+
if (event instanceof ErrorEvent) {
|
|
37
|
+
setCaughtError(event);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
setResourceError(event);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (event.type === 'unhandledrejection') {
|
|
44
|
+
setPromiseError(event);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* @description 设置错误信息
|
|
49
|
+
*/
|
|
50
|
+
function setCaughtError(event) {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
if (ignoreError(event.message)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let filename = event.filename, line = event.lineno, column = event.colno, functionName = '', stackFrame = JSON.stringify([]);
|
|
56
|
+
if (event.error && event.error.stack) {
|
|
57
|
+
const error = ErrorStackParser.parse(event.error);
|
|
58
|
+
stackFrame = JSON.stringify(error);
|
|
59
|
+
const info = error.find((item) => item.fileName && !/node_modules|vendor|bundle/.test(item.fileName));
|
|
60
|
+
if (info) {
|
|
61
|
+
filename = info.fileName;
|
|
62
|
+
line = Number(info.lineNumber);
|
|
63
|
+
column = Number(info.columnNumber);
|
|
64
|
+
functionName = info.functionName || '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
setReportValue('error', {
|
|
68
|
+
subType: 'js',
|
|
69
|
+
line,
|
|
70
|
+
column,
|
|
71
|
+
message: event.message,
|
|
72
|
+
filename,
|
|
73
|
+
functionName,
|
|
74
|
+
stack: ((_b = (_a = event === null || event === void 0 ? void 0 : event.error) === null || _a === void 0 ? void 0 : _a.stack) === null || _b === void 0 ? void 0 : _b.substring(0, Config.maxLength)) || '',
|
|
75
|
+
stackFrame,
|
|
76
|
+
events: getUserEvents(false),
|
|
77
|
+
});
|
|
78
|
+
report(getReport());
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* @description 设置异步错误信息
|
|
82
|
+
*/
|
|
83
|
+
function setPromiseError(event) {
|
|
84
|
+
let message = event.reason;
|
|
85
|
+
if (event.reason && typeof event.reason === 'object') {
|
|
86
|
+
message = JSON.stringify(event.reason);
|
|
87
|
+
if (message === '{}') {
|
|
88
|
+
message = event.reason.stack;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (ignoreError(message)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setReportValue('error', {
|
|
95
|
+
subType: 'async',
|
|
96
|
+
message,
|
|
97
|
+
events: getUserEvents(false),
|
|
98
|
+
});
|
|
99
|
+
report(getReport());
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* @description 设置资源错误信息
|
|
103
|
+
*/
|
|
104
|
+
function setResourceError(event) {
|
|
105
|
+
const target = event.target;
|
|
106
|
+
if (ignoreError(target.outerHTML)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
setReportValue('error', {
|
|
110
|
+
subType: 'resource',
|
|
111
|
+
message: target.outerHTML,
|
|
112
|
+
filename: target.src || target.currentSrc,
|
|
113
|
+
stack: target.localName.toUpperCase().substring(0, Config.maxLength),
|
|
114
|
+
events: getUserEvents(false),
|
|
115
|
+
});
|
|
116
|
+
report(getReport());
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 忽略的error
|
|
120
|
+
* @param errorMsg 错误信息
|
|
121
|
+
*/
|
|
122
|
+
function ignoreError(errorMsg) {
|
|
123
|
+
var _a;
|
|
124
|
+
const errors = ((_a = Config === null || Config === void 0 ? void 0 : Config.ignore) === null || _a === void 0 ? void 0 : _a.errors) || [];
|
|
125
|
+
const existIgnoreError = errors.findIndex((item) => {
|
|
126
|
+
if (typeof item === 'string') {
|
|
127
|
+
return item === errorMsg;
|
|
128
|
+
}
|
|
129
|
+
else if (Object.prototype.toString.call(item) === '[object Object]') {
|
|
130
|
+
if (item.regExp && typeof item.input === 'string') {
|
|
131
|
+
const regex = new RegExp(item.input, 'g');
|
|
132
|
+
return regex.test(errorMsg);
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return existIgnoreError > -1;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 获取用户最近得操作,视频录像时间应当在30秒到60秒之间
|
|
144
|
+
*/
|
|
145
|
+
let lastGetTime = Date.now(); //缓存操作录像,防止段时间内大量获取录像数据导滞内存溢出
|
|
146
|
+
function getUserEvents(notUseCache) {
|
|
147
|
+
let events;
|
|
148
|
+
if (Date.now() - lastGetTime > 2000 || notUseCache) {
|
|
149
|
+
lastGetTime = Date.now();
|
|
150
|
+
if (eventsMatrix.length >= 2) {
|
|
151
|
+
const finalVideoData = eventsMatrix[eventsMatrix.length - 1];
|
|
152
|
+
events = JSON.stringify(eventsMatrix[eventsMatrix.length - 2].concat(finalVideoData));
|
|
153
|
+
if (events.length > 1024 * 1024 * 15 && JSON.stringify(finalVideoData).length > 1024 * 1024) {
|
|
154
|
+
//如果录像数据量太大且最后那个分片的数据量不算小,那么缩短上报的录像时长
|
|
155
|
+
events = JSON.stringify(eventsMatrix[eventsMatrix.length - 1]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
events = JSON.stringify(eventsMatrix);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return events;
|
|
163
|
+
}
|
|
164
|
+
function getFullScreenShoot(filename) {
|
|
165
|
+
const name = filename || v4();
|
|
166
|
+
return html2canvas(document.body).then(function (canvas) {
|
|
167
|
+
var _a;
|
|
168
|
+
const urlData = canvas.toDataURL('image/png', 1);
|
|
169
|
+
const bytes = window.atob(urlData.split(',')[1]);
|
|
170
|
+
const mime = (_a = urlData.split(',')[0].match(/:(.*?);/)) === null || _a === void 0 ? void 0 : _a[1];
|
|
171
|
+
const ab = new ArrayBuffer(bytes.length);
|
|
172
|
+
const ia = new Uint8Array(ab);
|
|
173
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
174
|
+
ia[i] = bytes.charCodeAt(i);
|
|
175
|
+
}
|
|
176
|
+
const file = new File([ab], `${name}.png`, { type: mime });
|
|
177
|
+
return file;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export { getFullScreenShoot, getUserEvents, handleError, ignoreError, setCaughtError, setPromiseError, setResourceError };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 派发pushState, replaceState 的监听
|
|
3
|
+
* @param type
|
|
4
|
+
*/
|
|
5
|
+
export declare function _history(type: 'pushState' | 'replaceState'): () => void;
|
|
6
|
+
/**
|
|
7
|
+
* @description 处理hash变化
|
|
8
|
+
* @param e hash事件
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleHashChange(__e: Event): void;
|
|
11
|
+
/**
|
|
12
|
+
* @description 处理history变化
|
|
13
|
+
* @param e history事件
|
|
14
|
+
*/
|
|
15
|
+
export declare function handleHistoryChange(__e: Event): void;
|
|
16
|
+
export declare function setPVTime(): void;
|
|
17
|
+
/**
|
|
18
|
+
* @description 添加路由栈信息
|
|
19
|
+
* @param page 页面信息
|
|
20
|
+
*/
|
|
21
|
+
export declare function setRouteStack(page: string | string[]): {
|
|
22
|
+
originPage: string;
|
|
23
|
+
page: string;
|
|
24
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { setReportValue, getReport } from '../config/global.js';
|
|
2
|
+
import { report } from '../reporter.js';
|
|
3
|
+
import { Config } from '../config/index.js';
|
|
4
|
+
import { visualTrackFunc } from '../utils/index.js';
|
|
5
|
+
|
|
6
|
+
/* eslint-disable prefer-rest-params */
|
|
7
|
+
/** 路由栈数组,存储路由变化信息 */
|
|
8
|
+
let routerStack = [];
|
|
9
|
+
/** 路由触发的时间 */
|
|
10
|
+
let time = 0;
|
|
11
|
+
/**
|
|
12
|
+
* @description 派发pushState, replaceState 的监听
|
|
13
|
+
* @param type
|
|
14
|
+
*/
|
|
15
|
+
function _history(type) {
|
|
16
|
+
const origin = history[type];
|
|
17
|
+
return function () {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
const r = origin.apply(this, arguments);
|
|
20
|
+
const e = new Event(type);
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
e.arguments = arguments;
|
|
23
|
+
window.dispatchEvent(e);
|
|
24
|
+
return r;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @description 处理history变化
|
|
29
|
+
* @param e history事件
|
|
30
|
+
*/
|
|
31
|
+
function handleHistoryChange(__e) {
|
|
32
|
+
visualTrackFunc();
|
|
33
|
+
setReport();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 设置信息并触发上报
|
|
37
|
+
*/
|
|
38
|
+
function setReport() {
|
|
39
|
+
let pageUrl = location.href;
|
|
40
|
+
/**
|
|
41
|
+
* 此判断的逻辑是因为spa且hash模式下,路由搜索参数更改也会触发handleHistoryChange事件
|
|
42
|
+
* 所以路由栈中的信息只保存无搜索参数的地址
|
|
43
|
+
*/
|
|
44
|
+
if (Config.hash && Config.spa) {
|
|
45
|
+
const currentUrlSplit = location.href.split('#');
|
|
46
|
+
let currentUrlPostfix = '';
|
|
47
|
+
if (currentUrlSplit[1]) {
|
|
48
|
+
const index = currentUrlSplit[1].indexOf('?');
|
|
49
|
+
if (index > -1) {
|
|
50
|
+
currentUrlPostfix = currentUrlSplit[1].substring(0, index);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
currentUrlPostfix = currentUrlSplit[1];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const currentUrlResult = `${currentUrlSplit[0]}#${currentUrlPostfix}`;
|
|
57
|
+
pageUrl = currentUrlResult;
|
|
58
|
+
}
|
|
59
|
+
const { originPage, page } = setRouteStack(pageUrl);
|
|
60
|
+
// 愿页面等于当前页面,说明无路由变更。只有spa且hash模式下才会出现
|
|
61
|
+
if (originPage === page) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const currentTime = new Date().getTime();
|
|
65
|
+
// 页面停留时间
|
|
66
|
+
const stayTime = currentTime - time;
|
|
67
|
+
// 设置触发路由变化的时间
|
|
68
|
+
time = currentTime;
|
|
69
|
+
// 设置信息
|
|
70
|
+
setReportValue('originPage', originPage);
|
|
71
|
+
setReportValue('page', page);
|
|
72
|
+
setReportValue('type', 'pv');
|
|
73
|
+
setReportValue('pv', { stayTime });
|
|
74
|
+
// 上报数据
|
|
75
|
+
report(getReport());
|
|
76
|
+
}
|
|
77
|
+
function setPVTime() {
|
|
78
|
+
time = new Date().getTime();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* @description 添加路由栈信息
|
|
82
|
+
* @param page 页面信息
|
|
83
|
+
*/
|
|
84
|
+
function setRouteStack(page) {
|
|
85
|
+
if (typeof page === 'string') {
|
|
86
|
+
routerStack.push(page);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
routerStack = routerStack.concat(page);
|
|
90
|
+
}
|
|
91
|
+
// 路由栈只需保留两个来作为源页面和当前页面
|
|
92
|
+
routerStack = routerStack.slice(-2);
|
|
93
|
+
return {
|
|
94
|
+
originPage: routerStack[0],
|
|
95
|
+
page: routerStack[1] || routerStack[0],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { _history, handleHistoryChange, setPVTime, setRouteStack };
|