spy-client
Version:
spy client
1,275 lines (1,255 loc) • 60.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SpyClient = factory());
}(this, (function () { 'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
/**
* @file utils
* @author kaivean
*/
function assign() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var __assign = Object.assign || function __assign(t) {
for (var s = void 0, i = 1, n = arguments.length; i < n; i++) {
// eslint-disable-next-line prefer-rest-params
s = arguments[i];
for (var p in s) {
if (Object.prototype.hasOwnProperty.call(s, p)) {
t[p] = s[p];
}
}
}
return t;
};
return __assign.apply(this, args);
}
function getUrlInfoFromURL(url) {
if (URL && url) {
try {
var obj = new URL(url);
if (obj.host !== undefined) {
return {
protocol: obj.protocol,
host: obj.host,
pathname: obj.pathname,
ext: '',
};
}
}
catch (e) {
console.error(e);
}
}
return;
}
function getUrlInfo(url) {
var info = getUrlInfoFromURL(url);
if (!info) {
var parser = document.createElement('a');
parser.href = url;
info = {
protocol: parser.protocol,
host: parser.host || location.host,
pathname: parser.pathname,
ext: '',
};
}
var split = info.pathname.split('.');
info.ext = split[split.length - 1];
return info;
}
function f$1(n) {
return +n.toFixed(1);
}
function getResTiming(t) {
return {
wait: f$1(t.domainLookupStart - (t.navigationStart || t.fetchStart || t.startTime)),
dns: f$1(t.domainLookupEnd - t.domainLookupStart),
connect: f$1(t.connectEnd - t.connectStart),
req: f$1(t.responseStart - t.requestStart),
res: f$1(t.responseEnd - t.responseStart),
};
}
function getxpath(el) {
if (!el) {
return { xpath: '' };
}
var xpath = [];
while (el && el.nodeType === 1 && el !== el.parentNode) {
var t = el.tagName.toLowerCase();
if (el.getAttribute('id')) {
t += '[#' + el.getAttribute('id') + ']';
}
else if (el.classList && el.classList.length) {
t += '[.' + el.classList[el.classList.length - 1] + ']';
}
xpath.push(t);
if (el === document.body) {
break;
}
el = el.parentNode; // 修复缺陷检查
}
return {
xpath: xpath.join('<'),
};
}
/**
* @file SpyClient
* @author kaivean
*/
var defaultLogServer = 'https://sp1.baidu.com/5b1ZeDe5KgQFm2e88IuM_a/mwb2.gif?';
// 基础版本兼容非浏览器环境
var ver = navigator && navigator.userAgent ? navigator.userAgent.toLowerCase().match(/cpu iphone os (.*?)_/) : '';
var isLtIos14 = ver && ver[2] && (+ver[2] < 14);
function err(msg) {
console.error("[SpyClient_log]" + msg);
// throw new Error(msg);
}
function stringify(obj) {
return Object.keys(obj).map(function (key) {
var value = obj[key];
if (typeof value === 'undefined') {
value = '';
}
else if (typeof value !== 'string') {
value = JSON.stringify(value);
}
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}).join('&');
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
var SpyClient$1 = /** @class */ (function () {
function SpyClient(option) {
this.sample = {};
this.markCache = {};
if (!option.pid) {
throw new Error('pid is required');
}
this.option = {
pid: option.pid,
lid: option.lid,
check: option.check !== false,
sample: option.sample,
localCache: option.localCache,
logServer: option.logServer || defaultLogServer,
};
}
SpyClient.prototype.handle = function (logItem) {
if (!this.check(logItem)) {
return;
}
logItem = assign({
pid: this.option.pid,
lid: this.option.lid,
ts: Date.now(),
group: 'common',
}, logItem);
if (this.option.localCache) {
this.option.localCache.addLog(logItem);
}
// 当前api设置了抽样,
if (typeof logItem.sample === 'number') {
if (Math.random() > logItem.sample) {
return;
}
}
else if (typeof this.option.sample === 'number' && Math.random() > this.option.sample) { // 否则,用全局抽样
return;
}
delete logItem.sample;
return logItem;
};
SpyClient.prototype.send = function (data, post) {
if (post === void 0) { post = false; }
var logItems = isArray(data) ? data : [data];
var postData = [];
for (var _i = 0, logItems_1 = logItems; _i < logItems_1.length; _i++) {
var logItem = logItems_1[_i];
logItem = this.handle(logItem);
if (!logItem) {
continue;
}
postData.push(logItem);
// 期望通过post方式上传日志
if (post) {
continue;
}
var url = this.option.logServer + stringify(logItem);
this.request(url);
}
if (post) {
this.sendPost(postData);
}
};
SpyClient.prototype.check = function (query) {
if (!this.option.check) {
return true;
}
var types = ['perf', 'except', 'dist', 'count'];
if (types.indexOf(query.type) === -1) {
err('type only is one of ' + types.join(', '));
return false;
}
if (query.group && query.group.length > 30) {
err('group length execeeds 30');
return false;
}
var simpleReg = /^[a-zA-Z0-9-_]{0,30}$/;
if (query.type === 'except') {
if (!(typeof query.info.msg === 'string' && query.info.msg.length)) {
err('info.msg field must be not empty and is String');
return false;
}
}
else {
for (var _i = 0, _a = Object.keys(query.info); _i < _a.length; _i++) {
var infoKey = _a[_i];
if (!simpleReg.test(infoKey)) {
err("info." + infoKey + " is unexpected. "
+ 'Length must be not more than 30. '
+ 'Supported chars: a-zA-Z0-9-_');
return false;
}
var infoVal = query.info[infoKey];
if (query.type === 'dist') {
if (infoVal.length > 30) {
err("info." + infoKey + " value length execeeds 30 when type == 'dist'");
return false;
}
}
else if (typeof infoVal !== 'number') {
err("info." + infoKey + " value must be number");
return false;
}
}
}
if (query.dim) {
for (var _b = 0, _c = Object.keys(query.dim); _b < _c.length; _b++) {
var dimKey = _c[_b];
if (!simpleReg.test(dimKey)) {
err("dim key [" + dimKey + "] is unexpected. "
+ 'Length must be not more than 30. '
+ 'Supported chars: a-zA-Z0-9-_');
return false;
}
var dimVal = query.dim[dimKey];
if (!/^[a-zA-Z0-9\-_\*\.\s\/#\+@\&\u4e00-\u9fa5]{0,30}$/.test(dimVal)) {
err("dim." + dimKey + " value [" + dimVal + "] is unexpected. "
+ 'Length must be not more than 30. '
+ 'Supported chars: a-zA-Z0-9-_*. /#+@& and Chinese');
return false;
}
}
}
return true;
};
/**
*
* @param option 配置
*/
SpyClient.prototype.sendPerf = function (option) {
this.send(assign({
type: 'perf',
}, option));
};
/**
*
* @param option 错误配置项
*/
SpyClient.prototype.sendExcept = function (option) {
this.send(assign({
type: 'except',
}, option));
};
/**
*
* @param option 配置
*/
SpyClient.prototype.sendDist = function (option) {
this.send(assign({
type: 'dist',
}, option));
};
/**
*
* @param option 配置
*/
SpyClient.prototype.sendCount = function (option) {
this.send(assign({
type: 'count',
}, option));
};
/**
*
* @param e 错误实例
* @param option 错误配置项
*/
SpyClient.prototype.sendExceptForError = function (e, option) {
var newOpt = assign({}, option);
newOpt.info = assign({}, option.info || {}, {
msg: e.message,
stack: e.stack,
});
this.sendExcept(newOpt);
};
SpyClient.prototype.startMark = function (sign) {
this.markCache[sign] = {
start: Date.now(),
};
};
SpyClient.prototype.endMark = function (sign) {
if (this.markCache[sign]) {
this.markCache[sign].total = Date.now() - this.markCache[sign].start;
return this.markCache[sign].total;
}
return 0;
};
SpyClient.prototype.clearMark = function (sign) {
if (this.markCache[sign]) {
delete this.markCache[sign];
}
};
SpyClient.prototype.getAllMark = function () {
var ret = {};
for (var _i = 0, _a = Object.keys(this.markCache); _i < _a.length; _i++) {
var sign = _a[_i];
ret[sign] = this.markCache[sign].total;
}
return ret;
};
SpyClient.prototype.clearAllMark = function () {
this.markCache = {};
};
// send(data, true) 也能以post发送,但是会有严格校验
// sendPost能以post发送,但没有校验
SpyClient.prototype.sendPost = function (data) {
var logItems = isArray(data) ? data : [data];
var first = logItems[0];
// 需要在页面暴漏出来pid等基本信息,方式调试查看基本信息与兼容目前的监控
var query = {
pid: first.pid,
type: first.type,
group: first.group,
};
var url = this.option.logServer + stringify(query);
this.request(url, data);
};
// 有data时,意味着要用post发送请求
SpyClient.prototype.request = function (url, data) {
if (!(!isLtIos14
&& navigator
&& navigator.sendBeacon
&& navigator.sendBeacon(url, data ? JSON.stringify(data) : undefined))) {
if (data) {
this.fetch(url, data);
}
else {
(new Image()).src = url;
}
}
};
SpyClient.prototype.fetch = function (url, data) {
if (!fetch) {
err('Global fetch method doesn\'t exist');
return;
}
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
};
return SpyClient;
}());
/**
* @file FID
* @author kaivean
*/
var spyclient$3 = window.__spyHead;
var entryType$3 = 'first-input';
var FID = /** @class */ (function () {
function FID() {
var _this = this;
this.observer = null;
if (!window.PerformanceObserver) {
return;
}
if (spyclient$3 && spyclient$3.entryMap && spyclient$3.entryMap[entryType$3]) {
this.handle(spyclient$3.entryMap[entryType$3]);
}
try {
this.observer = new PerformanceObserver(function (list) {
_this.handle(list.getEntries());
});
this.observer.observe({ entryTypes: [entryType$3] });
}
catch (e) { }
}
FID.prototype.listenFID = function (cb) {
this.cb = cb;
this.callCB();
};
FID.prototype.callCB = function () {
if (this.finalValue !== undefined && this.cb) {
this.cb({ fid: this.finalValue });
}
};
FID.prototype.destroy = function () {
this.observer && this.observer.disconnect();
this.observer = null;
};
FID.prototype.handle = function (entries) {
var lastEntry = entries.pop();
if (lastEntry) {
this.finalValue = lastEntry.duration;
this.callCB();
this.destroy();
}
};
return FID;
}());
/**
* @file LayoutShift
* @author kaivean
*/
var spyclient$2 = window.__spyHead;
var entryType$2 = 'layout-shift';
var LayoutShift = /** @class */ (function () {
function LayoutShift() {
var _this = this;
this.observer = null;
this.onceLeave = false;
if (!window.PerformanceObserver) {
return;
}
// 先消费之前的
if (spyclient$2 && spyclient$2.entryMap && spyclient$2.entryMap[entryType$2]) {
this.handle(spyclient$2.entryMap[entryType$2]);
}
try {
this.observer = new PerformanceObserver(function (list) {
_this.handle(list.getEntries());
});
this.observer.observe({ entryTypes: [entryType$2] });
}
catch (e) { }
}
LayoutShift.prototype.listenLayoutShift = function (cb) {
this.cb = cb;
};
LayoutShift.prototype.leave = function () {
if (!this.onceLeave) {
this.onceLeave = true;
this.observer && this.observer.takeRecords && this.observer.takeRecords();
this.finalValue = this.value;
this.callCB();
this.destroy();
}
};
LayoutShift.prototype.callCB = function () {
if (this.finalValue !== undefined && this.cb) {
this.cb({ layoutShift: this.finalValue });
}
};
LayoutShift.prototype.destroy = function () {
this.observer && this.observer.disconnect();
this.observer = null;
};
LayoutShift.prototype.handle = function (entries) {
var _this = this;
entries.map(function (entry) {
// hadRecentInput 如果过去500毫秒内有用户输入,则返回 true, 刚进入页面内500ms,不会认为是layoutShift
if (!entry.hadRecentInput) {
_this.value = (_this.value || 0) + (entry.value || 0);
}
});
};
return LayoutShift;
}());
/**
* @file 简单的公共数据模块,内部模块之间数据交换
* @author kaivean
*/
var data = {};
function setData(key, value) {
data[key] = value;
}
function getData(key) {
return data[key];
}
/**
* @file LCP
* @author kaivean
*/
var spyclient$1 = window.__spyHead;
var entryType$1 = 'largest-contentful-paint';
var LCP = /** @class */ (function () {
function LCP() {
var _this = this;
this.observer = null;
if (!window.PerformanceObserver) {
return;
}
if (spyclient$1 && spyclient$1.entryMap && spyclient$1.entryMap[entryType$1]) {
this.handle(spyclient$1.entryMap[entryType$1]);
}
try {
// 仅在 img,image,svg,video,css url, block element with text nodes
this.observer = new PerformanceObserver(function (list) {
_this.handle(list.getEntries());
});
this.observer.observe({ entryTypes: [entryType$1] });
}
catch (e) { }
}
LCP.prototype.listenLCP = function (cb) {
this.cb = cb;
this.callCB();
};
LCP.prototype.callCB = function () {
if (this.finalValue && this.cb) {
this.cb({ lcp: this.finalValue });
}
};
LCP.prototype.load = function () {
this.observer && this.observer.takeRecords && this.observer.takeRecords();
this.finalValue = this.value;
setData('lcp', this.value);
this.callCB();
this.destroy();
};
LCP.prototype.destroy = function () {
this.observer && this.observer.disconnect();
this.observer = null;
};
LCP.prototype.handle = function (entries) {
var _this = this;
entries.map(function (entry) {
_this.value = entry.renderTime || entry.loadTime;
});
};
return LCP;
}());
function filterExcludeLogGifs(entry) {
return entry.name.indexOf('.gif?') < 0;
}
var TTI = /** @class */ (function () {
function TTI() {
this.lastLongTask = 0;
this.observer = null;
this.interval = 5000;
this.filterRequest = filterExcludeLogGifs;
this.observerCallback = this.observerCallback.bind(this);
this.ttiCheck = this.ttiCheck.bind(this);
}
TTI.prototype.check = function () {
return window.PerformanceObserver && performance && performance.timing && performance.timing.navigationStart;
};
TTI.prototype.listenTTI = function (cb, option) {
if (!this.check()) {
return;
}
// 监听获取longtask,持续20s
this.observeLongtask(20000);
this.cb = cb;
option = option || {};
if (option.interval) {
this.interval = option.interval;
}
if (option.filterRequest) {
this.filterRequest = option.filterRequest;
}
};
TTI.prototype.load = function () {
if (!this.check()) {
return;
}
// 每 5 秒检查一次是否符合TTI要求
this.ttiTimer = setInterval(this.ttiCheck, this.interval);
};
TTI.prototype.ttiCheck = function () {
if (!this.cb) {
return;
}
var now = performance.now();
if (now - this.lastLongTask < this.interval) {
// debug('tti 没有达成条件,上次 longTask 离现在差' + (now - this.lastLongTask) + 'ms');
return;
}
var networkSilence = this.getNetworkSilenceAt();
if (networkSilence === false) {
// debug('tti 没有达成条件,网络没有静默');
return;
}
if (now - networkSilence < this.interval) {
// debug('tti 没有达成条件,上次网络请求离现在差' + (now - networkSilence) + 'ms');
return;
}
// debug('tti 达成条件');
clearTimeout(this.ttiTimer);
var tti = this.lastLongTask;
// 没有longtask,就用dom ready时间作为tti,默认是认为dom ready已经绑定好时间,可以交换了
if (!tti) {
tti = performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart;
}
// 停止 long task 获取
this.stopObserveLongTask();
this.cb({
tti: tti,
});
};
TTI.prototype.observerCallback = function (list) {
var entries = list.getEntries();
var lastEntry = entries[entries.length - 1];
// debug('long task (no more than ' + lastEntry.duration + 'ms) detected');
// 最新的longTask完成时间
this.lastLongTask = lastEntry.startTime + lastEntry.duration;
};
TTI.prototype.stopObserveLongTask = function () {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
if (this.stopLongTaskTimeoutId) {
clearTimeout(this.stopLongTaskTimeoutId);
}
};
TTI.prototype.getNetworkSilenceAt = function () {
// const now = performance.now();
var resources = performance.getEntriesByType('resource')
.filter(this.filterRequest);
var lastResourceEnd = 0;
for (var _i = 0, resources_1 = resources; _i < resources_1.length; _i++) {
var item = resources_1[_i];
// 还没有responseEnd字段,说明网络请求还未结束
if (!item.responseEnd) {
return false;
}
// 取responseEnd最大值
if (item.responseEnd > lastResourceEnd) {
lastResourceEnd = item.responseEnd;
}
}
return lastResourceEnd;
};
TTI.prototype.observeLongtask = function (timeout) {
this.stopObserveLongTask();
try {
this.observer = new PerformanceObserver(this.observerCallback);
this.observer.observe({
entryTypes: ['longtask'],
});
this.stopLongTaskTimeoutId = setTimeout(this.stopObserveLongTask, timeout);
}
catch (e) { }
};
return TTI;
}());
/**
* @file LCP
* @author kaivean
*/
var Timing = /** @class */ (function () {
function Timing() {
}
// constructor() {}
Timing.prototype.listenTiming = function (cb) {
if (!window.performance || !window.performance.timing) {
// debug('no performance.timing api');
return;
}
this.cb = cb;
};
Timing.prototype.load = function () {
var _this = this;
setTimeout(function () {
_this.cb && _this.cb(_this.getMetric());
}, 500);
};
Timing.prototype.getMetric = function () {
var metric = {};
var timing = window.performance.timing;
var startTime = timing.navigationStart;
// 基础信息
metric.dns = timing.domainLookupEnd - timing.domainLookupStart;
metric.tcp = timing.connectEnd - timing.connectStart;
metric.request = timing.responseStart - timing.requestStart;
metric.response = timing.responseEnd - timing.responseStart;
// 这是从页面部分数据返回,浏览器开始解析doc元素到最底部的script脚本解析执行完成
// 脚本里触发的异步或绑定了更靠后的事件,不再纳入范围内
metric.parseHtml = timing.domInteractive - timing.domLoading;
// 很多事件绑定是在domContentLoaded事件里的,所以等其结束,一般页面元素的事件绑定好了,用户可以正确交互
// 当然存在在该加载事件之后绑定元素事件情况,但不再此考虑范围内
metric.domReady = timing.domContentLoadedEventEnd - startTime;
metric.loadEventHandle = timing.loadEventEnd - timing.loadEventStart;
// 基本该做的都做完,资源也都加载完成了
// 当然在onload事件处理函数里启动了异步方法,不再纳入范围内
metric.load = timing.loadEventEnd - startTime;
// 浏览器首次绘制与首次内容绘制, ios低版本无getEntriesByType api
if (performance.getEntriesByType) {
var paintEntries = performance.getEntriesByType('paint');
if (paintEntries && paintEntries.length) {
paintEntries.forEach(function (_a) {
var name = _a.name, duration = _a.duration, startTime = _a.startTime;
var time = Math.ceil(duration + startTime);
if (name === 'first-paint') {
metric.fp = time;
}
if (name === 'first-contentful-paint') {
metric.fcp = time;
}
});
}
}
// 端首次绘制与首屏
if (timing.domFirstPaint) {
metric.t7FirstPaint = timing.domFirstPaint - startTime;
}
if (timing.domFirstScreenPaint) {
metric.t7FirstScreen = timing.domFirstScreenPaint - startTime;
}
return metric;
};
return Timing;
}());
var ResType;
(function (ResType) {
ResType["JS"] = "js";
ResType["CSS"] = "css";
ResType["IMG"] = "img";
ResType["FONT"] = "font";
})(ResType || (ResType = {}));
// 字体
// svg使用场景比较灵活,可能作为一种字体格式,也可能当做图片使用
// 但因woff在所有现代浏览器中都支持,在css中使用iconfont时
// 往往优先加载woff格式字体文件,所以这里将svg划分为图片一类
var FontsTypes;
(function (FontsTypes) {
FontsTypes[FontsTypes["ttf"] = 0] = "ttf";
FontsTypes[FontsTypes["eot"] = 1] = "eot";
FontsTypes[FontsTypes["woff"] = 2] = "woff";
FontsTypes[FontsTypes["woff2"] = 3] = "woff2";
})(FontsTypes || (FontsTypes = {}));
// 忽略图片
// 下述图片路径往往是用来进行日志统计,不是正常图片
var ignoreDefaultPaths = [
'/mwb2.gif',
];
function ignorePath(url, paths) {
for (var _i = 0, paths_1 = paths; _i < paths_1.length; _i++) {
var path = paths_1[_i];
if (url.indexOf(path) > -1) {
return true;
}
}
return false;
}
function f(n) {
return +n.toFixed(1);
}
var Resource = /** @class */ (function () {
function Resource() {
this.trigger = 'load';
this.jsList = [];
this.cssList = [];
this.imgList = [];
this.fontList = [];
this.hostList = {};
this.bigImgList = {};
this.httpResList = {};
this.slowList = {};
this.resOption = { ignorePaths: [], trigger: 'load' };
this.bigImgOption = { ignorePaths: [], maxSize: 150 * 1024, trigger: 'load' };
this.httpResOption = { ignorePaths: [], trigger: 'load' };
this.slowOption = { ignorePaths: [], trigger: 'load', threshold: 1000 };
}
Resource.prototype.check = function () {
return performance && performance.getEntriesByType;
};
Resource.prototype.listenResource = function (cb, option) {
if (option === void 0) { option = {}; }
if (!this.check()) {
return;
}
this.resOption = assign(this.resOption, option);
this.trigger = this.resOption.trigger;
this.cb = cb;
};
Resource.prototype.listenBigImg = function (cb, option) {
if (option === void 0) { option = {}; }
if (!this.check()) {
return;
}
this.bigImgOption = assign(this.bigImgOption, option);
this.trigger = this.bigImgOption.trigger;
this.bigImgCB = cb;
};
Resource.prototype.listenHttpResource = function (cb, option) {
if (option === void 0) { option = {}; }
if (!this.check()) {
return;
}
this.httpResOption = assign(this.httpResOption, option);
this.trigger = this.httpResOption.trigger;
this.httpResCB = cb;
};
Resource.prototype.listenSlowResource = function (cb, option) {
if (option === void 0) { option = {}; }
if (!this.check()) {
return;
}
this.slowOption = assign(this.slowOption, option);
this.trigger = this.slowOption.trigger;
this.slowResCB = cb;
};
Resource.prototype.report = function () {
var _this = this;
var metricRes = this.getMetric();
if (metricRes) {
var metric = metricRes.metric, hostMetric = metricRes.hostMetric;
this.cb && this.cb(metric, hostMetric);
}
if (this.bigImgCB) {
// 发送大图监控数据
if (typeof window.requestIdleCallback === 'function' && Object.keys(this.bigImgList).length) {
window.requestIdleCallback(function () {
for (var _i = 0, _a = Object.keys(_this.bigImgList); _i < _a.length; _i++) {
var host = _a[_i];
for (var _b = 0, _c = _this.bigImgList[host]; _b < _c.length; _b++) {
var entry = _c[_b];
var timing = entry.timing;
var type = entry.type;
try {
var img = document.body.querySelector('img[src="' + timing.name + '"]');
_this.bigImgCB({
msg: timing.name,
dur: timing.duration,
xpath: getxpath(img).xpath,
host: host,
type: type,
});
}
catch (e) {
console.error(e);
}
}
}
});
}
}
if (this.httpResCB) {
// 发送http资源监控数据
if (typeof window.requestIdleCallback === 'function' && Object.keys(this.httpResList).length) {
window.requestIdleCallback(function () {
for (var _i = 0, _a = Object.keys(_this.httpResList); _i < _a.length; _i++) {
var host = _a[_i];
for (var _b = 0, _c = _this.httpResList[host]; _b < _c.length; _b++) {
var entry = _c[_b];
var timing = entry.timing;
var type = entry.type;
try {
var img = document.body.querySelector('[src="' + timing.name + '"]');
_this.httpResCB({
msg: timing.name,
dur: timing.duration,
xpath: getxpath(img).xpath,
host: host,
type: type,
});
}
catch (e) {
console.error(e);
}
}
}
});
}
}
if (this.slowResCB) {
// 发送慢资源监控数据
if (typeof window.requestIdleCallback === 'function' && Object.keys(this.slowList).length) {
window.requestIdleCallback(function () {
for (var _i = 0, _a = Object.keys(_this.slowList); _i < _a.length; _i++) {
var host = _a[_i];
for (var _b = 0, _c = _this.slowList[host]; _b < _c.length; _b++) {
var entry = _c[_b];
var timing = entry.timing;
var type = entry.type;
try {
var img = document.body.querySelector('[src="' + timing.name + '"]');
var info = getResTiming(timing);
_this.slowResCB(__assign(__assign({}, info), { dur: timing.duration, msg: timing.name, xpath: getxpath(img).xpath, host: host, type: type }));
}
catch (e) {
console.error(e);
}
}
}
});
}
}
};
Resource.prototype.load = function () {
var _this = this;
if (this.trigger !== 'load') {
return;
}
if (!this.check()) {
return;
}
setTimeout(function () {
_this.report();
}, 500);
};
Resource.prototype.leave = function () {
if (this.trigger !== 'leave') {
return;
}
this.report();
};
Resource.prototype.push = function (type, list, timing, urlInfo) {
if (!ignorePath(urlInfo.pathname, this.resOption.ignorePaths || [])) {
list.push(timing);
this.pushWithHost(type, this.hostList, timing, urlInfo);
}
if (!ignorePath(urlInfo.pathname, this.slowOption.ignorePaths || [])) {
if (timing.duration > this.slowOption.threshold) {
this.pushWithHost(type, this.slowList, timing, urlInfo);
}
}
};
Resource.prototype.pushWithHost = function (type, list, timing, urlInfo) {
var host = urlInfo.host;
if (!list[host]) {
list[host] = [];
}
list[host].push({
timing: timing,
type: type,
});
};
Resource.prototype.collectHttpResInHttps = function (type, timing, urlInfo) {
if (location.protocol === 'https:'
&& timing.name.indexOf('http://') === 0
&& !ignorePath(urlInfo.pathname, this.httpResOption.ignorePaths || [])) {
this.pushWithHost(type, this.httpResList, timing, urlInfo);
}
};
// 有些jsonp也属于script,这里只统计js后缀的文件
Resource.prototype.addScript = function (timing, urlInfo) {
if (urlInfo.ext === 'js') {
if (timing.decodedBodySize !== 0) {
this.push('js', this.jsList, timing, urlInfo);
}
}
};
// 暂时将css文件或代码块发起的请求归位三类(主要为这两类)
// 1、加载字体
// 2、加载背景图(图片不容易区分,有的没有明确后缀名)
// 3、光标文件(后缀为.cur,这里也划分为图片)
// (svg当做图片,前述已说明)
Resource.prototype.addResFromCss = function (timing, urlInfo) {
if (urlInfo.ext && FontsTypes.hasOwnProperty(urlInfo.ext)) {
this.push('font', this.fontList, timing, urlInfo);
}
else {
this.addImg(timing, urlInfo);
}
};
// link一般加载css资源
// 也可以通过preload可以预下载一些资源
// 这里只统计js类型的preload
Resource.prototype.addLink = function (timing, urlInfo) {
if (urlInfo.ext === 'css') {
this.push('css', this.cssList, timing, urlInfo);
}
// preload as script
else if (urlInfo.ext === 'js') {
this.push('js', this.jsList, timing, urlInfo);
}
};
Resource.prototype.addImg = function (timing, urlInfo) {
this.push('img', this.imgList, timing, urlInfo);
// 大于指定size的图片采集
if (timing.decodedBodySize > (this.bigImgOption.maxSize || 0)
&& !ignorePath(urlInfo.pathname, this.bigImgOption.ignorePaths || [])) {
this.pushWithHost('img', this.bigImgList, timing, urlInfo);
}
};
Resource.prototype.handleTimings = function (list) {
var len = list.length;
for (var i = 0; i < len; i++) {
var timing = list[i];
var urlInfo = getUrlInfo(timing.name);
if (ignorePath(urlInfo.pathname, ignoreDefaultPaths)) {
continue;
}
switch (list[i].initiatorType) {
case 'script':
this.addScript(timing, urlInfo);
break;
case 'css':
this.addResFromCss(timing, urlInfo);
break;
case 'img':
this.addImg(timing, urlInfo);
break;
case 'link':
this.addLink(timing, urlInfo);
break;
case 'audio':
this.collectHttpResInHttps('audio', timing, urlInfo);
break;
case 'video':
this.collectHttpResInHttps('video', timing, urlInfo);
break;
}
}
};
Resource.prototype.getNumAndSize = function (type, list) {
var obj = {};
var num = type + 'Num';
var size = type + 'Size';
var transferSize = type + 'TransferSize';
var cacheRate = type + 'CacheRate';
var duration = type + 'Duration';
obj[num] = 0;
obj[size] = 0;
obj[transferSize] = 0;
var totalDurationTime = 0;
list.forEach(function (timing) {
obj[num]++;
obj[size] += (timing.decodedBodySize / 1024);
obj[transferSize] += (timing.transferSize / 1024);
totalDurationTime += timing.duration;
});
obj[duration] = f(obj[num] > 0 ? totalDurationTime / obj[num] : 0);
if (obj[size]) {
var diff = obj[size] - obj[transferSize];
obj[cacheRate] = f(diff >= 0 ? 100 * diff / obj[size] : 0);
}
obj[size] = f(obj[size]);
obj[transferSize] = f(obj[transferSize]);
return obj;
};
Resource.prototype.getMetric = function () {
// 原来代码
var mainPageTiming = performance.getEntriesByType('navigation')[0];
var resourceTimings = performance.getEntriesByType('resource');
if (mainPageTiming && resourceTimings && resourceTimings.length) {
this.handleTimings(resourceTimings);
var metric = __assign(__assign(__assign(__assign({}, this.getNumAndSize(ResType.JS, this.jsList)), this.getNumAndSize(ResType.CSS, this.cssList)), this.getNumAndSize(ResType.IMG, this.imgList)), this.getNumAndSize(ResType.FONT, this.fontList));
// 主文档大小
var pageTiming = mainPageTiming;
metric.docSize = f(pageTiming.decodedBodySize / 1024);
metric.docTransferSize = f(pageTiming.transferSize / 1024);
metric.headerSize = f((pageTiming.transferSize - pageTiming.encodedBodySize || 0) / 1024);
metric.allSize = f(metric.docSize
+ metric.jsSize
+ metric.cssSize
+ metric.imgSize
+ metric.fontSize);
metric.allTransferSize = f(metric.docTransferSize
+ metric.jsTransferSize
+ metric.cssTransferSize
+ metric.imgTransferSize
+ metric.fontTransferSize);
var hostMetric = {};
for (var _i = 0, _a = Object.keys(this.hostList); _i < _a.length; _i++) {
var host = _a[_i];
var timings = this.hostList[host].map(function (item) { return item.timing; });
hostMetric[host] = this.getNumAndSize('host', timings);
}
return { metric: metric, hostMetric: hostMetric };
}
return;
};
return Resource;
}());
/**
* @file Memory
* @author kaivean
*/
var initMemory = null;
if (window.performance && window.performance.memory) {
initMemory = window.performance.memory;
}
var Memory = /** @class */ (function () {
function Memory() {
this.onceLeave = false;
}
// constructor() {}
Memory.prototype.listenMemory = function (cb) {
this.cb = cb;
};
Memory.prototype.leave = function () {
if (!this.onceLeave && initMemory) {
this.onceLeave = true;
var curMemory = window.performance.memory;
// fix 早期浏览器的memroy api 值不更新的问题,将此情况排除
if (curMemory.usedJSHeapSize === initMemory.usedJSHeapSize
&& curMemory.totalJSHeapSize === initMemory.totalJSHeapSize) {
return;
}
var memory = window.performance.memory;
this.cb && this.cb({
usedJSHeapSize: memory.usedJSHeapSize / 1024,
totalJSHeapSize: memory.totalJSHeapSize / 1024,
jsHeapSizeLimit: memory.jsHeapSizeLimit / 1024,
usedJSHeapRate: 100 * memory.usedJSHeapSize / memory.totalJSHeapSize,
});
}
};
return Memory;
}());
/**
* @file NavigatorInfo
* @author kaivean
*/
var NavigatorInfo = /** @class */ (function () {
function NavigatorInfo() {
}
// private cb: NavigatorInfoCB;
NavigatorInfo.prototype.getNavigatorInfo = function () {
// this.cb = cb;
var ret = {};
var dataConnection = navigator.connection;
if (typeof dataConnection === 'object') {
ret = {
downlink: dataConnection.downlink,
effectiveType: dataConnection.effectiveType,
rtt: dataConnection.rtt,
saveData: !!dataConnection.saveData, // 数据节约模式
};
}
// 内存 G
if (navigator.deviceMemory) {
ret.deviceMemory = navigator.deviceMemory;
}
// 核数
if (navigator.hardwareConcurrency) {
ret.hardwareConcurrency = navigator.deviceMemory;
}
return ret;
};
NavigatorInfo.prototype.load = function () { };
return NavigatorInfo;
}());
/**
* @file Longtask
* @author kaivean
*/
var spyclient = window.__spyHead;
var entryType = 'longtask';
var Longtask = /** @class */ (function () {
function Longtask() {
var _this = this;
this.lts = [];
this.observer = null;
this.onceLeave = false;
if (spyclient && spyclient.entryMap && spyclient.entryMap[entryType]) {
this.lts = this.lts.concat(spyclient.entryMap[entryType]);
}
try {
this.observer = new PerformanceObserver(function (list) {
_this.lts = _this.lts.concat(list.getEntries());
});
// buffered 兼容性太差
this.observer.observe({ entryTypes: [entryType] });
}
catch (e) { }
}
Longtask.prototype.check = function () {
return window.PerformanceObserver && performance && performance.timing && performance.timing.navigationStart;
};
Longtask.prototype.listenFSPLongTask = function (cb) {
if (!this.check()) {
return;
}
this.fspCB = cb;
};
Longtask.prototype.listenLCPLongTask = function (cb) {
if (!this.check()) {
return;
}
this.lcpCB = cb;
};
Longtask.prototype.listenLoadLongTask = function (cb) {
if (!this.check()) {
return;
}
this.loadCB = cb;
};
Longtask.prototype.listenPageLongTask = function (cb) {
if (!this.check()) {
return;
}
this.pageCB = cb;
};
Longtask.prototype.load = function () {
var _this = this;
if (!this.check()) {
return;
}
var data = this.getStatData(Date.now());
// lcp的值存入data模块也是在load期间,这里延迟一会再获取
setTimeout(function () {
_this.loadCB && _this.loadCB({
loadLongtaskTime: data.time,
loadTBT: data.tbt,
loadTotalTime: data.totalTime,
loadLongtaskRate: data.rate,
loadLongtaskNum: data.num,
});
if (performance.timing && performance.timing.domFirstScreenPaint) {
var data_1 = _this.getStatData(performance.timing.domFirstScreenPaint);
_this.fspCB && _this.fspCB({
fspLongtaskTime: data_1.time,
fspTBT: data_1.tbt,
fspTotalTime: data_1.totalTime,
fspLongtaskRate: data_1.rate,
fspLongtaskNum: data_1.num,
});
}