monitor-track 1.13.0 → 1.15.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/cjs/index.js CHANGED
@@ -40,12 +40,16 @@ const Config = {
40
40
  reportUrl: '',
41
41
  projectID: '',
42
42
  maxLength: 1000,
43
- spa: false,
44
- hash: false,
43
+ spa: true,
44
+ hash: true,
45
45
  enableBehavior: true,
46
46
  enableError: true,
47
+ enableErrorScreenshot: false,
48
+ enableErrorEvent: false,
47
49
  enableVisualTrack: false,
48
- enableLagTrack: false,
50
+ enableLagTrack: true,
51
+ enableRecord: false,
52
+ enableOnlinePersons: true,
49
53
  ignore: {
50
54
  urls: [],
51
55
  errors: [],
@@ -57,27 +61,39 @@ const Config = {
57
61
  * @param config 配置项
58
62
  */
59
63
  function setConfig(config) {
64
+ var _a;
60
65
  Object.assign(Config, config);
66
+ if (!Config.ignore)
67
+ Config.ignore = {};
68
+ if (Array.isArray((_a = Config.ignore) === null || _a === void 0 ? void 0 : _a.errors)) {
69
+ ['ResizeObserver loop limit exceeded', 'ResizeObserver loop completed with undelivered notifications.', 'Cancel'].forEach((item) => {
70
+ if (!Config.ignore.errors.includes(item)) {
71
+ Config.ignore.errors.push(item);
72
+ }
73
+ });
74
+ }
61
75
  }
62
76
 
63
- var version = "1.13.0";
77
+ var version = "1.15.0";
64
78
 
65
79
  const eventsMatrix = [[]];
66
- rrweb__namespace.record({
67
- emit(event, isCheckout) {
68
- // isCheckout 是一个标识,告诉你重新制作了快照
69
- if (isCheckout) {
70
- eventsMatrix.push([]);
71
- }
72
- if (eventsMatrix.length > 2) {
73
- eventsMatrix.shift();
74
- }
75
- const lastEvents = eventsMatrix[eventsMatrix.length - 1];
76
- lastEvents.push(event);
77
- },
78
- // 每30秒重新制作快照
79
- checkoutEveryNms: 30 * 1000,
80
- });
80
+ function enableRecordFunc() {
81
+ rrweb__namespace.record({
82
+ emit(event, isCheckout) {
83
+ // isCheckout 是一个标识,告诉你重新制作了快照
84
+ if (isCheckout) {
85
+ eventsMatrix.push([]);
86
+ }
87
+ if (eventsMatrix.length > 2) {
88
+ eventsMatrix.shift();
89
+ }
90
+ const lastEvents = eventsMatrix[eventsMatrix.length - 1];
91
+ lastEvents.push(event);
92
+ },
93
+ // 30秒重新制作快照
94
+ checkoutEveryNms: 30 * 1000,
95
+ });
96
+ }
81
97
  /**
82
98
  * @description 错误事件触发后的操作
83
99
  */
@@ -835,6 +851,8 @@ const reportMap = {
835
851
  detail: 'de',
836
852
  /** 上报时带上的请求参数 */
837
853
  requestData: 'rD',
854
+ /** 上报时带上的响应结果 */
855
+ responseBody: 'rB',
838
856
  /** 上报时带上的请求方法 */
839
857
  method: 'rM',
840
858
  },
@@ -903,7 +921,7 @@ function reportFunc(data) {
903
921
  delete ERROR_MESSAGE_MAP[message];
904
922
  }, 3000);
905
923
  const { p, pid, host, bN, pt } = payload;
906
- if ((_c = data.error) === null || _c === void 0 ? void 0 : _c.events) {
924
+ if (((_c = data.error) === null || _c === void 0 ? void 0 : _c.events) && Config.enableErrorEvent) {
907
925
  hasErrorEvent = true;
908
926
  const errorEvent = data.error.events;
909
927
  const delayTime = Math.ceil(Math.random() * 10000);
@@ -914,17 +932,19 @@ function reportFunc(data) {
914
932
  });
915
933
  }, delayTime < 3500 ? 3500 : delayTime);
916
934
  }
917
- picturePromise = getFullScreenShoot(filename)
918
- .then((file) => {
919
- return new Promise((res) => {
920
- xhrFunc(file.name, data.uuid, filename, file, res, (result) => {
921
- payload.error.pic = result;
922
- }, { p, pid, host, bN, pt });
935
+ if (Config.enableErrorScreenshot) {
936
+ picturePromise = getFullScreenShoot(filename)
937
+ .then((file) => {
938
+ return new Promise((res) => {
939
+ xhrFunc(file.name, data.uuid, filename, file, res, (result) => {
940
+ payload.error.pic = result;
941
+ }, { p, pid, host, bN, pt });
942
+ });
943
+ })
944
+ .catch((err) => {
945
+ payload.error.picError = (err === null || err === void 0 ? void 0 : err.stack) || (err === null || err === void 0 ? void 0 : err.toString());
923
946
  });
924
- })
925
- .catch((err) => {
926
- payload.error.picError = (err === null || err === void 0 ? void 0 : err.stack) || (err === null || err === void 0 ? void 0 : err.toString());
927
- });
947
+ }
928
948
  }
929
949
  }
930
950
  return picturePromise
@@ -961,6 +981,10 @@ function reportFunc(data) {
961
981
  // 上报
962
982
  function report(data) {
963
983
  return new Promise((res) => {
984
+ var _a;
985
+ if ((((_a = Config.ignore) === null || _a === void 0 ? void 0 : _a.urls) || []).some((url) => location.href.includes(url))) {
986
+ return;
987
+ }
964
988
  //在帧的空闲时间上报
965
989
  const dataCopy = JSON.parse(JSON.stringify(data));
966
990
  if (typeof window.requestIdleCallback === 'function') {
@@ -1219,171 +1243,429 @@ function getReport() {
1219
1243
  customPayload: Config.customPayload,
1220
1244
  dpr: window.devicePixelRatio,
1221
1245
  });
1222
- }
1223
- function ajaxEventTrigger(event) {
1224
- const ajaxEvent = new CustomEvent(event, {
1225
- detail: this,
1246
+ }
1247
+
1248
+ let reconnectAndSendTimeout, websocketHeartBeatInterval, ws, userId = '';
1249
+ let remoteDebugEnable = false;
1250
+ let targetPlatform = '';
1251
+ let targetUserId = '';
1252
+ const originConsole = Object.assign({}, console);
1253
+ const formatConsole = () => {
1254
+ if (remoteDebugEnable)
1255
+ return;
1256
+ remoteDebugEnable = true;
1257
+ window.console = Object.assign({}, originConsole);
1258
+ const arr = ['debug', 'log', 'info', 'warn', 'error'];
1259
+ arr.forEach((item) => {
1260
+ //@ts-ignore
1261
+ window.console[item] = function (...args) {
1262
+ //@ts-ignore
1263
+ originConsole[item](...args);
1264
+ const data = {
1265
+ origin: 'console',
1266
+ level: item,
1267
+ userId,
1268
+ time: new Date().toLocaleString(),
1269
+ page: location.href,
1270
+ content: '',
1271
+ targetPlatform,
1272
+ targetUserId,
1273
+ };
1274
+ try {
1275
+ const content = JSON.stringify(args);
1276
+ data.content = content;
1277
+ }
1278
+ catch (err) {
1279
+ data.content = 'console内容解析错误: ' + err.stack || err.message || err.toString();
1280
+ }
1281
+ const msg = {
1282
+ type: WEBSOCKET_TYPE.DEBUG_INFO_UPLOAD,
1283
+ userId,
1284
+ platform: 'monitor-track-sdk',
1285
+ data,
1286
+ };
1287
+ wsSendFunc(msg);
1288
+ };
1226
1289
  });
1227
- window.dispatchEvent(ajaxEvent);
1228
- }
1229
- const OldXHR = window.XMLHttpRequest;
1230
- function newXHR() {
1231
- const realXHR = new OldXHR();
1232
- realXHR.addEventListener('loadstart', function () {
1233
- ajaxEventTrigger.call(this, 'ajaxLoadStart');
1234
- }, false);
1235
- realXHR.addEventListener('loadend', function () {
1236
- ajaxEventTrigger.call(this, 'ajaxLoadEnd');
1237
- }, false);
1238
- // 此处的捕获的异常会连日志接口也一起捕获,如果日志上报接口异常了,就会导致死循环了。
1239
- realXHR.onerror = function (e) {
1240
- // eslint-disable-next-line no-console
1241
- console.warn('realXHR.onerror, e', e);
1242
- };
1243
- return realXHR;
1290
+ };
1291
+ const resetConsole = () => {
1292
+ window.console = originConsole;
1293
+ remoteDebugEnable = false;
1294
+ };
1295
+ const uploadRequest = (requestInfo) => {
1296
+ // console.log('monitor-track-sdk: uploadRequest remoteDebugEnable', remoteDebugEnable);
1297
+ if (remoteDebugEnable) {
1298
+ const data = {
1299
+ origin: 'request',
1300
+ userId,
1301
+ time: new Date().toLocaleString(),
1302
+ page: location.href,
1303
+ content: requestInfo,
1304
+ targetPlatform,
1305
+ targetUserId,
1306
+ };
1307
+ const msg = {
1308
+ type: WEBSOCKET_TYPE.DEBUG_INFO_UPLOAD,
1309
+ userId,
1310
+ platform: 'monitor-track-sdk',
1311
+ data,
1312
+ };
1313
+ // console.log('monitor-track-sdk: request-upload sent');
1314
+ wsSendFunc(msg);
1315
+ }
1316
+ };
1317
+ function enableOnlinePersonsFunc() {
1318
+ initWebsocket();
1244
1319
  }
1245
- /**
1246
- * 页面接口请求监控
1247
- */
1248
- const tempUrlInfo = {};
1249
- const recordXMLHttpRequestLog = (XMLHttpRequestTimeout) => {
1250
- XMLHttpRequestTimeout = typeof XMLHttpRequestTimeout === 'number' ? XMLHttpRequestTimeout : 1000;
1251
- const timeRecordArray = [];
1252
- //@ts-ignore
1253
- window.__XMLHttpRequest__ = window.XMLHttpRequest;
1254
- //@ts-ignore
1255
- window.XMLHttpRequest = newXHR;
1256
- window.addEventListener('ajaxLoadStart', function (e) {
1257
- const tempObj = {
1258
- timeStamp: new Date().getTime(),
1259
- event: e,
1320
+ const WEBSOCKET_TYPE = {
1321
+ TRY_CONNECT: 'try-connect',
1322
+ CHECK_CONNECT: 'check-connect',
1323
+ PING: 'ping',
1324
+ PONG: 'pong',
1325
+ REPLY_SERVER_HEART_BEAT: 'reply-server-heart-beat',
1326
+ SOCKET_HEART_BEAT: 'socket-heart-beat',
1327
+ RESPONSE_DATE: 'response-date',
1328
+ REMOTE_DEBUG_ENABLE: 'remote-debug-enable',
1329
+ DEBUG_INFO_UPLOAD: 'debug-info-upload',
1330
+ REPlY_MESSAGE: 'reply-message',
1331
+ };
1332
+ let mounted = false;
1333
+ const initWebsocket = () => {
1334
+ if (window.WebSocket) {
1335
+ userId = localStorage.getItem('username') || getUid();
1336
+ closeWebsocket('');
1337
+ websocketHeartBeatInterval = setInterval(reconnectAndSend, 30000);
1338
+ const isLocal = Config.reportUrl.includes('http://');
1339
+ ws = new WebSocket(`ws${isLocal ? '' : 's'}://${Config.reportUrl.replace(isLocal ? 'http://' : 'https://', '').replace('/s/r', '')}?type=monitor-dsk`);
1340
+ ws.onopen = () => openWS();
1341
+ ws.onmessage = (data) => incomingMessage(data);
1342
+ ws.onerror = () => {
1343
+ ws.close(1000);
1344
+ if (websocketHeartBeatInterval)
1345
+ clearInterval(websocketHeartBeatInterval);
1346
+ websocketHeartBeatInterval = setInterval(reconnectAndSend, 10000);
1260
1347
  };
1261
- timeRecordArray.push(tempObj);
1262
- });
1263
- window.addEventListener('ajaxLoadEnd', function () {
1264
- const timeRecordArrayCopy = [].concat(timeRecordArray);
1265
- for (let i = 0; i < timeRecordArrayCopy.length; i++) {
1266
- if (timeRecordArrayCopy[i].event.detail && timeRecordArrayCopy[i].event.detail.status > 0) {
1267
- const currentTime = new Date().getTime();
1268
- const { responseURL, status, statusText, timeStamp } = timeRecordArrayCopy[i].event.detail;
1269
- const previousTime = timeStamp || timeRecordArrayCopy[i].timeStamp;
1270
- const loadTime = currentTime - previousTime;
1271
- const request = {
1272
- requestType: 'xhr',
1273
- responseURL,
1274
- status,
1275
- loadTime,
1276
- statusText,
1277
- reason: '',
1278
- detail: '',
1279
- requestData: '',
1280
- method: '',
1348
+ ws.onclose = () => {
1349
+ ws.close(1000);
1350
+ if (websocketHeartBeatInterval)
1351
+ clearInterval(websocketHeartBeatInterval);
1352
+ websocketHeartBeatInterval = setInterval(reconnectAndSend, 10000);
1353
+ };
1354
+ if (!mounted) {
1355
+ reconnectNetwork();
1356
+ mounted = true;
1357
+ }
1358
+ }
1359
+ };
1360
+ const openWS = () => {
1361
+ const data = getReport();
1362
+ const { page, projectID, host, pageTitle } = data;
1363
+ const msg = {
1364
+ type: WEBSOCKET_TYPE.TRY_CONNECT,
1365
+ userId,
1366
+ platform: 'monitor-track-sdk',
1367
+ data: {
1368
+ p: page,
1369
+ pid: projectID,
1370
+ host,
1371
+ pt: pageTitle,
1372
+ platform: 'web',
1373
+ },
1374
+ };
1375
+ wsSendFunc(msg);
1376
+ };
1377
+ const reconnectAndSend = () => {
1378
+ try {
1379
+ if (ws.readyState !== 1) {
1380
+ reconnectSocket();
1381
+ }
1382
+ else {
1383
+ const msg = {
1384
+ type: WEBSOCKET_TYPE.CHECK_CONNECT,
1385
+ userId,
1386
+ data: WEBSOCKET_TYPE.PING,
1387
+ date: Date.now(),
1388
+ };
1389
+ wsSendFunc(msg, 'reconnectAndSend');
1390
+ // 20秒超时,如果收不到服务端pong响应则表示服务端已主动断开连接,此时需客户端重新开启websocket连接
1391
+ reconnectAndSendTimeout = setTimeout(() => {
1392
+ reconnectSocket();
1393
+ }, 20000);
1394
+ }
1395
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1396
+ }
1397
+ catch (_err) {
1398
+ // console.error('monitor-track-sdk: reconnectAndSend err', err);
1399
+ }
1400
+ };
1401
+ const closeWebsocket = (info) => {
1402
+ if (ws) {
1403
+ ws.onopen = null;
1404
+ ws.onmessage = null;
1405
+ ws.onerror = null;
1406
+ ws.onclose = null;
1407
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
1408
+ ws.close(1000, info);
1409
+ }
1410
+ //@ts-ignore
1411
+ ws = null; // 释放引用
1412
+ }
1413
+ if (websocketHeartBeatInterval)
1414
+ clearInterval(websocketHeartBeatInterval);
1415
+ };
1416
+ const reconnectSocket = () => {
1417
+ // console.warn('monitor-track-sdk: 正在重新建立websocket连接...');
1418
+ initWebsocket();
1419
+ };
1420
+ const incomingMessage = (e) => __awaiter(void 0, void 0, void 0, function* () {
1421
+ let data = {};
1422
+ try {
1423
+ data = JSON.parse(e.data);
1424
+ // console.info('incomingMessage data', data);
1425
+ switch (data.type) {
1426
+ case WEBSOCKET_TYPE.PONG:
1427
+ // console.info('monitor-track-sdk: incomingMessage 响应客户端的心跳: ping => pong');
1428
+ clearTimeout(reconnectAndSendTimeout);
1429
+ break;
1430
+ case WEBSOCKET_TYPE.SOCKET_HEART_BEAT: {
1431
+ // console.info('monitor-track-sdk: incomingMessage 响应服务端的心跳: heart beat => ', new Date().toLocaleString());
1432
+ const msg = {
1433
+ type: WEBSOCKET_TYPE.CHECK_CONNECT,
1434
+ userId,
1435
+ data: WEBSOCKET_TYPE.REPLY_SERVER_HEART_BEAT,
1436
+ date: Date.now(),
1281
1437
  };
1282
- if (loadTime && loadTime > XMLHttpRequestTimeout) {
1283
- request.reason = 'slow';
1284
- request.detail = `request is too slow, XMLHttpRequestTimeout: ${loadTime}`;
1285
- }
1286
- else if (status && status >= 300 && statusText && statusText.toLowerCase() !== 'ok') {
1287
- request.reason = 'failed';
1288
- request.detail = `request is failed, status: ${status}, statusText: ${statusText}`;
1438
+ wsSendFunc(msg, 'socket-heart-beat reply-server-heart-beat');
1439
+ break;
1440
+ }
1441
+ case WEBSOCKET_TYPE.RESPONSE_DATE:
1442
+ // console.info(`monitor-track-sdk: incomingMessage WS connected, Roundtrip time: ${Date.now() - data.data} ms`);
1443
+ break;
1444
+ case WEBSOCKET_TYPE.REMOTE_DEBUG_ENABLE: {
1445
+ // console.log('monitor-track-sdk REMOTE_DEBUG_ENABLE data.data', data.data);
1446
+ const { enable, platform, userId } = data.data;
1447
+ if (enable && platform && userId) {
1448
+ targetPlatform = platform;
1449
+ targetUserId = userId;
1450
+ formatConsole();
1289
1451
  }
1290
- else ;
1291
- if (request.reason) {
1292
- if (!tempUrlInfo[responseURL]) {
1293
- tempUrlInfo[responseURL] = true;
1294
- setTimeout(() => {
1295
- delete tempUrlInfo[responseURL];
1296
- }, 10);
1297
- setReportValue('error', null);
1298
- report(Object.assign(Object.assign({}, getReport()), {
1299
- page: location.href,
1300
- originPage: location.href,
1301
- type: 'request',
1302
- req: request,
1303
- }));
1304
- }
1452
+ else {
1453
+ resetConsole();
1305
1454
  }
1455
+ break;
1456
+ }
1457
+ case WEBSOCKET_TYPE.REPlY_MESSAGE: {
1458
+ // eslint-disable-next-line no-console
1459
+ console.info('incomingMessage REPlY_MESSAGE, data: ', data.data);
1460
+ break;
1306
1461
  }
1307
- // 当前请求成功后就在数组中移除掉
1308
- timeRecordArray.splice(i, 1);
1462
+ default:
1463
+ // console.error('incomingMessage default error', data);
1464
+ break;
1309
1465
  }
1310
- });
1466
+ }
1467
+ catch (err) {
1468
+ // eslint-disable-next-line no-console
1469
+ console.error('monitor-track-sdk incomingMessage JSON.parse', err);
1470
+ }
1471
+ });
1472
+ const wsSendFunc = (msg, _info) => {
1473
+ if (ws.readyState === 1) {
1474
+ msg.platform = 'monitor-track-sdk';
1475
+ const message = JSON.stringify(msg);
1476
+ ws.send(message);
1477
+ }
1478
+ else {
1479
+ // console.warn('monitor-track-sdk: window.ws.readyState === 1 info', info);
1480
+ reconnectSocket();
1481
+ }
1311
1482
  };
1312
- const hackFetch = (XMLHttpRequestTimeout) => {
1313
- XMLHttpRequestTimeout = typeof XMLHttpRequestTimeout === 'number' ? XMLHttpRequestTimeout : 1000;
1314
- if (typeof window.fetch === 'function') {
1315
- const __fetch__ = window.fetch;
1316
- window.__fetch__ = __fetch__;
1317
- //@ts-ignore
1318
- window.fetch = function (t, ...args) {
1319
- const begin = Date.now();
1320
- //禁用数组的扩展运算符,否则portal的生产环境会报Uncaught TypeError: Object(...) is not a function,
1321
- //编译后的__spreadArray函数有问题,而这个函数来自于tslib.具体报错原因不知.
1322
- //其他平台使用数组的扩展运算符没有问题,比如前端监控平台的管理界面
1323
- const params = [].concat(t).concat(args);
1324
- return __fetch__
1325
- .apply(window, params)
1326
- .then(function (res) {
1327
- const response = res.clone();
1328
- const headers = response.headers;
1329
- if (headers && typeof headers.get === 'function') {
1330
- const ct = headers.get('content-type');
1331
- if (ct && !/(text)|(json)/.test(ct)) {
1332
- return res;
1333
- }
1483
+ const reconnectNetwork = () => {
1484
+ let networkFlag = false;
1485
+ let debounceTimeout = null;
1486
+ document.addEventListener('offline', function () {
1487
+ // 断开网络
1488
+ try {
1489
+ // console.info('monitor-track-sdk: 客户端网络已断开,正在关闭websocket');
1490
+ ws.close(1000);
1491
+ networkFlag = true;
1492
+ debounceTimeout = setTimeout(() => {
1493
+ // console.warn('monitor-track-sdk: reconnectNetwork offline 无法访问网络');
1494
+ // message.error('无法访问网络');
1495
+ }, 5000);
1496
+ }
1497
+ catch (err) {
1498
+ // console.error('monitor-track-sdk: addEventListener offline after setTimeout', err);
1499
+ }
1500
+ }, false);
1501
+ document.addEventListener('online', function () {
1502
+ // 连接到网络
1503
+ try {
1504
+ if (networkFlag === true) {
1505
+ clearTimeout(debounceTimeout);
1506
+ // console.info('monitor-track-sdk: addEventListener reconnectNetwork online');
1507
+ // message.success('网络已恢复');
1508
+ networkFlag = false;
1509
+ // console.warn('monitor-track-sdk: addEventListener online websocket 正在重新建立连接...');
1510
+ initWebsocket();
1511
+ }
1512
+ }
1513
+ catch (err) {
1514
+ // console.error('monitor-track-sdk: document.addEventListener online', err);
1515
+ }
1516
+ }, false);
1517
+ };
1518
+
1519
+ let xhrTimeout = 2500;
1520
+ let fetchTimeout = 2500;
1521
+ const maxContentLength = 1000;
1522
+ // 保存原始 XMLHttpRequest
1523
+ const OriginalXHR = window.XMLHttpRequest;
1524
+ // 创建增强版的 XMLHttpRequest 类
1525
+ class InterceptedXHR extends OriginalXHR {
1526
+ constructor() {
1527
+ super();
1528
+ this.requestInfo = {
1529
+ requestType: 'xhr',
1530
+ method: '',
1531
+ responseURL: '',
1532
+ requestData: null,
1533
+ responseBody: null,
1534
+ status: 0,
1535
+ startTime: Date.now(),
1536
+ loadTime: 0,
1537
+ statusText: '',
1538
+ reason: '',
1539
+ detail: '',
1540
+ };
1541
+ this.setupInterceptors();
1542
+ }
1543
+ open(method, url, async = true, username, password) {
1544
+ super.open(method, url, async, username, password);
1545
+ this.requestInfo.method = method;
1546
+ this.requestInfo.responseURL = url;
1547
+ }
1548
+ // eslint-disable-next-line no-undef
1549
+ send(body) {
1550
+ super.send(body);
1551
+ try {
1552
+ this.requestInfo.requestData =
1553
+ typeof body === 'object' && body
1554
+ ? JSON.stringify(body).slice(0, maxContentLength)
1555
+ : typeof body === 'string'
1556
+ ? body.slice(0, maxContentLength)
1557
+ : body || '';
1558
+ }
1559
+ catch (err) {
1560
+ // eslint-disable-next-line no-console
1561
+ console.error('xhr send err: ', err, 'body', body);
1562
+ }
1563
+ this.requestInfo.startTime = Date.now();
1564
+ }
1565
+ setupInterceptors() {
1566
+ this.addEventListener('load', () => {
1567
+ try {
1568
+ this.requestInfo.status = this.status;
1569
+ this.requestInfo.responseBody = this.responseText.slice(0, maxContentLength);
1570
+ this.requestInfo.loadTime = Date.now() - this.requestInfo.startTime;
1571
+ this.requestInfo.statusText = this.statusText;
1572
+ if (this.requestInfo.loadTime > xhrTimeout) {
1573
+ this.requestInfo.reason = 'slow';
1574
+ this.requestInfo.detail = `request is too slow, XMLHttpRequestTimeout: ${this.requestInfo.loadTime}`;
1334
1575
  }
1335
- const loadTime = Date.now() - begin;
1336
- response
1337
- .text()
1338
- .then(function (result) {
1339
- const { url, status, statusText, ok } = response;
1340
- const request = {
1341
- requestType: 'fetch',
1342
- responseURL: url,
1343
- status,
1344
- loadTime,
1345
- statusText,
1346
- reason: '',
1347
- detail: '',
1348
- requestData: '',
1349
- method: '',
1350
- };
1351
- if (!ok || status >= 300) {
1352
- request.reason = 'failed';
1353
- request.detail = `request is failed, status: ${status}, statusText: ${statusText}`;
1354
- }
1355
- else if (loadTime > XMLHttpRequestTimeout) {
1356
- request.reason = 'slow';
1357
- request.detail = `request is too slow, XMLHttpRequestTimeout: ${loadTime}, result: ${result === null || result === void 0 ? void 0 : result.slice(0, 500)}`;
1358
- }
1359
- if (request.reason) {
1360
- if (!tempUrlInfo[url]) {
1361
- tempUrlInfo[url] = true;
1362
- setTimeout(() => {
1363
- delete tempUrlInfo[url];
1364
- }, 10);
1365
- setReportValue('error', null);
1366
- report(Object.assign(Object.assign({}, getReport()), {
1367
- page: location.href,
1368
- originPage: location.href,
1369
- type: 'request',
1370
- req: request,
1371
- }));
1372
- }
1373
- }
1374
- })
1375
- .catch((err) => {
1376
- // eslint-disable-next-line no-console
1377
- console.log('hackFetch response.text() err', err);
1378
- });
1379
- return res;
1380
- })
1381
- .catch((err) => {
1576
+ if (this.status >= 400) {
1577
+ this.requestInfo.reason = 'failed';
1578
+ this.requestInfo.detail = `request is failed, status: ${this.status}, statusText: ${this.statusText}`;
1579
+ }
1580
+ if (this.requestInfo.reason) {
1581
+ setReportValue('error', null);
1582
+ report(Object.assign(Object.assign({}, getReport()), {
1583
+ page: location.href,
1584
+ originPage: location.href,
1585
+ type: 'request',
1586
+ req: this.requestInfo,
1587
+ }));
1588
+ }
1589
+ uploadRequest(this.requestInfo);
1590
+ }
1591
+ catch (err) {
1382
1592
  // eslint-disable-next-line no-console
1383
- console.log('hackFetch err', err);
1384
- });
1385
- };
1593
+ console.error('xhr load err: ', err, 'this.requestInfo', this.requestInfo);
1594
+ }
1595
+ });
1596
+ this.addEventListener('error', () => {
1597
+ // console.error('❌ 请求失败:', this.requestInfo);
1598
+ });
1386
1599
  }
1600
+ }
1601
+ const recordXMLHttpRequest = (XMLHttpRequestTimeout) => {
1602
+ xhrTimeout = XMLHttpRequestTimeout;
1603
+ // 重写全局 XMLHttpRequest
1604
+ window.XMLHttpRequest = InterceptedXHR;
1605
+ };
1606
+ const originalFetch = window.fetch;
1607
+ // eslint-disable-next-line no-undef
1608
+ const fetchFUnc = function (input, init) {
1609
+ return __awaiter(this, void 0, void 0, function* () {
1610
+ const url = typeof input === 'string' ? input : input.toString();
1611
+ const options = init || {};
1612
+ const requestInfo = {
1613
+ requestType: 'fetch',
1614
+ method: options.method || 'GET',
1615
+ responseURL: url,
1616
+ requestData: typeof options.body === 'object' && options.body ? JSON.stringify(options.body).slice(0, maxContentLength) : options.body || '',
1617
+ responseBody: '',
1618
+ status: 0,
1619
+ startTime: Date.now(),
1620
+ loadTime: 0,
1621
+ statusText: '',
1622
+ reason: '',
1623
+ detail: '',
1624
+ };
1625
+ const response = yield originalFetch(input, init);
1626
+ try {
1627
+ const clonedResponse = response.clone();
1628
+ requestInfo.status = response.status;
1629
+ requestInfo.loadTime = Date.now() - requestInfo.startTime;
1630
+ requestInfo.statusText = response.statusText;
1631
+ if (!response.ok) {
1632
+ requestInfo.reason = 'failed';
1633
+ requestInfo.detail = `request is failed, status: ${response.status}, statusText: ${response.statusText}`;
1634
+ }
1635
+ else {
1636
+ if (requestInfo.loadTime > fetchTimeout) {
1637
+ requestInfo.reason = 'slow';
1638
+ requestInfo.detail = `request is too slow, XMLHttpRequestTimeout: ${requestInfo.loadTime}`;
1639
+ }
1640
+ }
1641
+ try {
1642
+ const responseBody = yield clonedResponse.text();
1643
+ requestInfo.responseBody = responseBody.slice(0, maxContentLength);
1644
+ }
1645
+ catch (e) {
1646
+ requestInfo.responseBody = (e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : e.stack) || (e === null || e === void 0 ? void 0 : e.toString());
1647
+ }
1648
+ if (requestInfo.reason) {
1649
+ setReportValue('error', null);
1650
+ report(Object.assign(Object.assign({}, getReport()), {
1651
+ page: location.href,
1652
+ originPage: location.href,
1653
+ type: 'request',
1654
+ req: requestInfo,
1655
+ }));
1656
+ }
1657
+ uploadRequest(requestInfo);
1658
+ }
1659
+ catch (err) {
1660
+ // eslint-disable-next-line no-console
1661
+ console.error('❌ 获取响应数据失败, requestInfo: ', requestInfo, 'err: ', err);
1662
+ }
1663
+ return response;
1664
+ });
1665
+ };
1666
+ const recordFetch = (XMLHttpRequestTimeout) => {
1667
+ fetchTimeout = XMLHttpRequestTimeout;
1668
+ window.fetch = fetchFUnc;
1387
1669
  };
1388
1670
 
1389
1671
  class Track {
@@ -1398,8 +1680,10 @@ class Track {
1398
1680
  this.listenPageLag = () => {
1399
1681
  this.observer = new PerformanceObserver((list) => {
1400
1682
  list.getEntries().forEach((entry) => {
1401
- if (entry.duration > (Config.lagTimeout || 400)) {
1683
+ if (entry.duration > (Config.lagTimeout || 800)) {
1402
1684
  handlePageLag(entry.duration);
1685
+ // eslint-disable-next-line no-console
1686
+ console.log('监测到页面卡顿:', entry.duration, entry, location.href);
1403
1687
  }
1404
1688
  });
1405
1689
  });
@@ -1407,7 +1691,6 @@ class Track {
1407
1691
  };
1408
1692
  }
1409
1693
  init(config) {
1410
- var _a;
1411
1694
  // 是否开启日志收集
1412
1695
  if (!config || !config.enable) {
1413
1696
  return;
@@ -1424,18 +1707,17 @@ class Track {
1424
1707
  console.warn('缺少上报地址!');
1425
1708
  return;
1426
1709
  }
1427
- if (this.ignoreUrl(((_a = config === null || config === void 0 ? void 0 : config.ignore) === null || _a === void 0 ? void 0 : _a.urls) || [])) {
1428
- return;
1429
- }
1430
1710
  setConfig(config);
1431
1711
  initReport();
1432
- recordXMLHttpRequestLog(config.XMLHttpRequestTimeout);
1433
- hackFetch(config.XMLHttpRequestTimeout);
1712
+ recordXMLHttpRequest(config.XMLHttpRequestTimeout || 2500);
1713
+ recordFetch(config.XMLHttpRequestTimeout || 2500);
1434
1714
  Config.spa && this.addListenRouterChange();
1435
1715
  Config.enableBehavior && this.addListenUserActivity();
1436
1716
  Config.enableError && this.addListenJSUncaught();
1437
1717
  Config.enableVisualTrack && this.visualTrack();
1438
1718
  Config.enableLagTrack && this.listenPageLag();
1719
+ Config.enableRecord && enableRecordFunc();
1720
+ Config.enableOnlinePersons && enableOnlinePersonsFunc();
1439
1721
  this.addListenUnload();
1440
1722
  initWindowObjectFunction();
1441
1723
  }
@@ -1475,14 +1757,6 @@ class Track {
1475
1757
  this.destroy();
1476
1758
  });
1477
1759
  }
1478
- /**
1479
- * 忽略的url
1480
- * @param urls
1481
- */
1482
- ignoreUrl(urls) {
1483
- const someUrl = urls.some((url) => location.href.includes(url));
1484
- return someUrl;
1485
- }
1486
1760
  /**
1487
1761
  * @description 销毁监听器
1488
1762
  */
@@ -1505,6 +1779,7 @@ class Track {
1505
1779
  this.lagTimer && clearInterval(this.lagTimer);
1506
1780
  (_a = this.observer) === null || _a === void 0 ? void 0 : _a.disconnect();
1507
1781
  sessionStorage.removeItem(monitorTrackSessionId);
1782
+ closeWebsocket('destroy');
1508
1783
  }
1509
1784
  }
1510
1785