UNPKG

fe-supervisor

Version:

The frontend monitor project

1,297 lines (1,294 loc) 58 kB
(function(g,f){typeof exports==='object'&&typeof module!=='undefined'?f(exports):typeof define==='function'&&define.amd?define(['exports'],f):(g=g||self,f(g.$sv={}));}(this,function(exports){'use strict';/** * @param {AnyEventName} event event name * @param {function} fn event callback * @param {object} dom event dom * @param {ListenerOption} option option contain captrue, passive, once */ var addListener = (function () { if (typeof window === 'undefined') return function () { }; if (!window.addEventListener) { return function (event, fn, dom) { var eventDOM = dom || window; eventDOM.attachEvent("on" + event, fn); }; } return function (event, fn, dom, option) { if (option === void 0) { option = {}; } var eventDOM = dom || window; var _a = option.capture, capture = _a === void 0 ? false : _a, _b = option.passive, passive = _b === void 0 ? false : _b, _c = option.once, once = _c === void 0 ? false : _c; eventDOM.addEventListener(event, fn, { capture: capture, passive: passive, once: once }); }; })();/** * copy text to clipboard method * @param {string} text the content for copy * @param {function} cb the callback for copy result * @return {void} */ function __awaiter(thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }function compatCheck(type) { var res = true; try { switch (type) { case 'generator': var genFn = function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, true]; case 1: _a.sent(); return [2 /*return*/]; } }); }; var gen = genFn(); gen.next(); break; case 'promise': Promise.resolve(true); break; case 'async': var asyncFn = function () { return __awaiter(this, void 0, void 0, function () { var res; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, Promise.resolve(true).then(function (res) { return res; })]; case 1: res = _a.sent(); return [2 /*return*/, res]; } }); }); }; asyncFn(); break; } } catch (error) { res = false; } return res; }/** * get cookie by appoint name and cookie str * @param {string} name * @param {string} cookie * @return {string | null} */ function getTs() { return (Date.now && +Date.now()) || new Date().getTime(); }/** * get element type * @param {any} ele the target element * @return {string} the element type */ function getUA(u) { if (!u) return false; function check(pattern) { return (pattern).test(u); } function parseIPhoneX(u) { var model = ''; if (/(iPhone\/)?iPhone\s?X/.test(u)) { // 通过UA判断 model = 'iPhoneX'; } else { // 浏览器中无法通过UA检测出iphonex系列 if (/iPhone/.test(u)) { // 通过ua加屏幕宽高判断 // https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/ // iPhone X @3x 1125px × 2436px => 375px x 812px // iPhone XS @3x 1125px × 2436px => 375px x 812px // iPhone XS Max @3x 1242px × 2688px => 414px x 896px // iPhone XR @2x 828px × 1792px => 414px x 896px // Portrait/Landscape if (typeof screen !== 'undefined') { var screenHeight = screen.height; var screenWidth = screen.width; if (screenHeight === 812 || screenWidth === 812) { // iPhoneX/iPhoneXS model = 'iPhoneX'; } else if (screenHeight === 896 || screenWidth === 896) { // iPhoneXR/iPhoneXSMax model = 'iPhoneXR'; if (devicePixelRatio) { if (devicePixelRatio === 3) { model = 'iPhoneXSMax'; } } } } } } return model; } var UA = { isAndroid: check(/android|linux|adr/i), isIOS: check(/\(i[^;]+;( U;)? CPU.+Mac OS X/i), isWindows: check(/window/i), isMac: check(/mac os x/i), isIPad: check(/iPad/i), isMobile: check(/AppleWebKit.*Mobile.*|Mobile/i), isWebKit: check(/webkit\W/i), isChrome: check(/webkit\W.*(chrome|chromium)\W/i), isFirefox: check(/mozilla.*\Wfirefox\W/i), isGecko: check(/mozilla(?!.*webkit).*\Wgecko\W/i), is360se: check(/360/i), isIE: (function () { if (typeof window !== 'undefined' && navigator.appName === 'Microsoft Internet Explorer') { return true; } else if (check(/\bTrident\b/)) { return true; } else { return false; } })(), isEdge: check(/\bEdge\b/i), isOpera: check(/opera.*\Wpresto\W|OPR/i), isSafari: check(/webkit\W(?!.*chrome).*safari\W/i), isUCBrowser: check(/ucbrowser/i), isBaiduBrowser: check(/bidubrowser/i), isSougouBrowser: check(/metasr/i), isLiebaoBrowser: check(/lbbrowser/i), isWeixin: check(/micromessenger/i), isSinaWeibo: check(/weibo/i), isQQ: check(/qq/i), isQQWeibo: check(/tencentmicroblog/i), isFacebook: check(/fban/i), isTwitter: check(/twitter/i), isInstagram: check(/instagram/i), isIphoneX: parseIPhoneX(u) == 'iPhoneX' ? true : false, isIPhoneXR: parseIPhoneX(u) == 'iPhoneXR' ? true : false, isIPhoneXSMax: parseIPhoneX(u) == 'iPhoneXSMax' ? true : false, }; return UA; }var _this = undefined; var gen = function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/]; case 1: _a.sent(); return [2 /*return*/]; } }); }; var typeMap = { string: '', number: 1, boolean: true, null: null, undefined: undefined, symbol: Symbol(1), array: [], object: {}, regexp: /regexp/, date: new Date(), function: function () { }, promise: Promise.resolve(void (0)), generatorfunction: gen, generator: gen(), asyncfunction: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/]; }); }); } }; /** * judgement ele type * @param {string} type the string of ele type for judgement * @param {any} ele the target element * @return {boolean} whether or not ele pair with type */ function isType(type) { return function (ele) { if (typeof ele !== 'object') return typeof ele === type.toLowerCase(); var len = Object.prototype.toString.call(ele).length - 1; return Object.prototype.toString.call(ele).slice(8, len).toLowerCase() === type.toLowerCase(); }; }var logLevelSet; (function (logLevelSet) { logLevelSet[logLevelSet["detail"] = 0] = "detail"; logLevelSet[logLevelSet["info"] = 1] = "info"; logLevelSet[logLevelSet["warn"] = 2] = "warn"; logLevelSet[logLevelSet["error"] = 3] = "error"; logLevelSet[logLevelSet["silent"] = 4] = "silent"; })(logLevelSet || (logLevelSet = {})); var parseUrl = (function () { if (typeof window === 'undefined') return function (url) { return url; }; var HTTP_PORT = '80'; var HTTPS_PORT = '443'; var DEFAULT_PORT = RegExp(':(' + HTTP_PORT + '|' + HTTPS_PORT + ')$'); var a = document.createElement('a'); var cache = {}; /** * Parses the given url and returns an object mimicing a `Location` object. * @param {string} url The url to parse. * @return {!Object} An object with the same properties as a `Location`. */ return function parse_url(url) { // All falsy values (as well as ".") should map to the current URL. url = (!url || url == '.') ? location.href : url; if (cache[url]) return cache[url]; a.href = url; // When parsing file relative paths (e.g. `../index.html`), IE will correctly // resolve the `href` property but will keep the `..` in the `path` property. // It will also not include the `host` or `hostname` properties. Furthermore, // IE will sometimes return no protocol or just a colon, especially for things // like relative protocol URLs (e.g. "//google.com"). // To workaround all of these issues, we reparse with the full URL from the // `href` property. if (url.charAt(0) == '.' || url.charAt(0) == '/') return parse_url(a.href); // Don't include default ports. var port = (a.port == HTTP_PORT || a.port == HTTPS_PORT) ? '' : a.port; // PhantomJS sets the port to "0" when using the file: protocol. port = port == '0' ? '' : port; // Sometimes IE incorrectly includes a port for default ports // (e.g. `:80` or `:443`) even when no port is specified in the URL. // http://bit.ly/1rQNoMg var host = a.host.replace(DEFAULT_PORT, ''); // Not all browser support `origin` so we have to build it. var origin = a.origin ? a.origin : a.protocol + '//' + host; // Sometimes IE doesn't include the leading slash for pathname. // http://bit.ly/1rQNoMg var pathname = a.pathname.charAt(0) == '/' ? a.pathname : '/' + a.pathname; return cache[url] = { hash: a.hash, host: host, hostname: a.hostname, href: a.href, origin: origin, pathname: pathname, port: port, protocol: a.protocol, search: a.search, }; }; })();/** * @param {event} event event object */ var removeListener = (function () { if (typeof window === 'undefined') return function () { }; if (!window.removeEventListener) { return function (event, fn, dom) { var eventDOM = dom || window; eventDOM.detachEvent("on" + event, fn); }; } return function (event, fn, dom, useCapture) { if (useCapture === void 0) { useCapture = false; } var eventDOM = dom || window; eventDOM.removeEventListener(event, fn, useCapture); }; })();/** * @param {event} event event object */ var storage = (function () { if (typeof window === 'undefined' || !window.sessionStorage || !window.localStorage) return { get: function () { return null; }, set: function () { return false; }, clear: function () { return false; } }; var handleStorage = { sessionStorage: { set: function (key, val) { var res; try { window.sessionStorage.setItem(key, val); res = true; } catch (err) { res = false; } return res; }, get: function (key) { var res; try { res = window.sessionStorage.getItem(key); } catch (err) { res = null; } return res; }, clear: function (key) { var res; try { window.sessionStorage.removeItem(key); res = true; } catch (err) { res = false; } return res; } }, localStorage: { set: function (key, val) { var res; try { window.localStorage.setItem(key, val); res = true; } catch (err) { res = false; } return res; }, get: function (key) { var res; try { res = window.localStorage.getItem(key); } catch (err) { res = null; } return res; }, clear: function (key) { var res; try { window.localStorage.removeItem(key); res = true; } catch (err) { res = false; } return res; } }, cookie: { set: function (key, val, option) { var res; try { var _a = option || {}, _b = _a.domain, domain = _b === void 0 ? '' : _b, _c = _a.path, path = _c === void 0 ? '/' : _c, _d = _a.expires, expires = _d === void 0 ? 0 : _d, _e = _a.secure, secure = _e === void 0 ? false : _e; var ts = void 0; if (expires >= 0) { ts = new Date(); ts.setTime(ts.getTime() + expires * 1000); } document.cookie = key + "=" + val + ";domain=" + domain + ";path=" + path + ";expires=" + (ts ? ts.toUTCString() : '') + ";" + (secure ? 'secure' : ''); res = true; } catch (err) { res = false; } return res; }, get: function (key) { var reg = new RegExp('(^| )' + key + '=([^;]*)(;|$)'); var cookArr = document.cookie.match(reg); if (cookArr && cookArr[2]) { return unescape(cookArr[2]); } return null; }, clear: function (key, domain) { document.cookie = key + "=\"\";domain=" + domain + ";max-age=-1"; var res = handleStorage.cookie.get(key); return !res; } }, get: function (key, storeType) { try { if (typeof storeType === 'string' && handleStorage[storeType]) { return handleStorage[storeType].get(key); } return this.cookie.get(key) || this.localStorage.get(key) || this.sessionStorage.get(key); } catch (e) { return null; } }, set: function (key, val, storeType, option) { var res; try { if (typeof storeType === 'string' && handleStorage[storeType]) { res = handleStorage[storeType].set(key, val, option); } else if (option) { res = handleStorage.cookie.set(key, val, option); } else { res = handleStorage.localStorage.set(key, val); } } catch (e) { res = false; } return res; }, clear: function (key, storeType, domain) { var res; try { if (typeof storeType === 'string' && handleStorage[storeType]) { res = handleStorage[storeType].clear(key, domain); } else if (domain) { res = handleStorage.cookie.clear(key, domain); } else { res = handleStorage.localStorage.clear(key) && handleStorage.sessionStorage.clear(key); } } catch (e) { res = false; } return res; } }; return handleStorage; })();var longTaskThreshold = 50; var singleTaskThreshold = longTaskThreshold / 2; var notGF = function () { console.warn('Please pass the [Generator Function]'); return false; }; var timeslice = (function () { var supportAPI = typeof window !== 'undefined' && window.performance; return function (genF) { if (!genF || typeof genF !== 'function') return notGF; var gen = genF(); if (typeof gen.next !== 'function') return notGF; return new Promise(function (resolve, reject) { function next() { try { var start = (supportAPI && performance.now()) || getTs(); var res = null; do { res = gen.next(); } while (!res.done && ((supportAPI && performance.now()) || getTs()) - start < singleTaskThreshold); if (res.done) { resolve(res.value); return; } setTimeout(next); } catch (err) { reject(err); } } next(); }); }; })();function uuid() { function S4() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()); }function notSupport() { console.warn('Your browser not support the [Performance] API!'); return false; } function notSupportPromisify() { console.warn('Your browser not support the [Performance] API!'); return Promise.resolve(false); }class Observer { constructor(target, callback) { this.cache = []; this.observer = this.init(target, callback); } init(target, callback) { if (!target) { console.warn('Please pass a vaild HTMLElement'); return null; } let observer; try { observer = new MutationObserver(callback); observer.observe(target, { 'childList': true, 'subtree': true, 'attributes': true, 'attributeFilter': ['src', 'href'] }); } catch (error) { return null; } return observer; } cancel() { this.observer && this.observer.disconnect(); } setCache(cache) { this.cache.push(cache); } getCache() { return this.cache; } clearCache() { this.cache.splice(0); } }var ExceptionTypes; (function (ExceptionTypes) { ExceptionTypes[ExceptionTypes["js"] = 0] = "js"; ExceptionTypes[ExceptionTypes["api"] = 1] = "api"; ExceptionTypes[ExceptionTypes["source"] = 2] = "source"; })(ExceptionTypes || (ExceptionTypes = {})); const jsErrors = []; const apiErrors = []; const sourceErrors = []; const errorTag = 'fe-supervisor-error'; function getErrors(type) { const typeIndex = type ? ExceptionTypes[type] : undefined; let errors; switch (typeIndex) { case ExceptionTypes['js']: errors = jsErrors; break; case ExceptionTypes['api']: errors = apiErrors; break; case ExceptionTypes['source']: errors = sourceErrors; break; default: errors = { jsErrors, apiErrors, sourceErrors }; } return errors; } const HandleException = { getErrors, setErrors(error) { const { type } = error; const typeIndex = ExceptionTypes[type]; switch (typeIndex) { case ExceptionTypes['js']: const callstack = new Error().stack; jsErrors.push({ ...error, code_site: callstack }); break; case ExceptionTypes['api']: apiErrors.push(error); break; case ExceptionTypes['source']: sourceErrors.push(error); break; } }, clearError(type) { const typeIndex = type ? ExceptionTypes[type] : undefined; let res = true; try { switch (typeIndex) { case ExceptionTypes['js']: jsErrors.splice(0); break; case ExceptionTypes['api']: apiErrors.splice(0); break; case ExceptionTypes['source']: sourceErrors.splice(0); break; default: jsErrors.splice(0); apiErrors.splice(0); sourceErrors.splice(0); } } catch (err) { res = false; } return res; }, async dupliError(type, msg) { let res = false; const errs = HandleException.getErrors(type); const len = (errs && errs.length) || 0; if (compatCheck('generator')) { function* gen() { for (let i = 0; i < len; i++) { const item = errs[i]; if (item.msg && item.msg === msg) { res = true; return; } yield; } } await timeslice(gen); return res; } else { for (let i = 0; i < len; i++) { const item = errs[i]; if (item.msg && item.msg === msg) { res = true; break; } } return res; } } };async function catchError(type, msg) { if (!type) return; const isDuplication = await HandleException.dupliError(type, msg); if (!isDuplication) { HandleException.setErrors({ ts: getTs(), type, url: location.href, msg }); } }const markCache = []; const measureCache = []; const marks = []; const measures = []; const getMemory = (function () { if (typeof window === 'undefined' || !window.performance) return notSupport; return function () { let limit, used, total, usedRatio; try { const p = window.performance; const m = p.memory || {}; limit = m.jsHeapSizeLimit || 1; used = m.usedJSHeapSize || 0; total = m.totalJSHeapSize || 1; usedRatio = +Number.prototype.toFixed.call(used / total, 3); } catch (error) { const msg = `[SV - getMemory]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return { limit, memory: usedRatio || 'N/A', used: usedRatio ? used : 'N/A', total: usedRatio ? total : 'N/A' }; } }; })(); const getTiming = (function () { if (typeof window === 'undefined' || !window.performance) return notSupport; return function () { let wscreen, fscreen, network, network_prev, network_redirect, network_dns, network_tcp, network_request, network_response, network_interact, dom_loading, dom_interact, dom_ready, dom_load, dom_complete, js_ready, js_load, js_complete, render_ready, render_load, total; wscreen = fscreen = network = network_prev = network_redirect = network_dns = network_tcp = network_request = network_response = network_interact = dom_loading = dom_interact = dom_ready = dom_load = dom_complete = js_ready = js_load = js_complete = render_ready = render_load = total = 'N/A'; try { const p = window.performance; const t = p.timing || {}; wscreen = timingFilter(t.domLoading - t.navigationStart); fscreen = timingFilter(t.domContentLoadedEventStart - t.navigationStart); network = timingFilter(t.responseEnd - t.navigationStart); network_prev = timingFilter(t.fetchStart - t.navigationStart); network_redirect = timingFilter(t.redirectEnd - t.redirectStart); network_dns = timingFilter(t.domainLookupEnd - t.domainLookupStart); network_tcp = timingFilter(t.connectEnd - t.connectStart); network_request = timingFilter(t.responseStart - t.requestStart); network_response = timingFilter(t.responseEnd - t.responseStart); network_interact = timingFilter(t.responseEnd - t.requestStart); dom_loading = timingFilter(t.domLoading - t.responseEnd); dom_interact = timingFilter(t.domInteractive - t.domLoading); dom_ready = timingFilter(t.domContentLoadedEventStart - t.domLoading); dom_load = timingFilter(t.domComplete - t.domLoading); dom_complete = timingFilter(t.domComplete - t.navigationStart); js_ready = timingFilter(t.domContentLoadedEventStart - t.domInteractive); js_load = timingFilter(t.domContentLoadedEventEnd - t.domInteractive); js_complete = timingFilter(t.domContentLoadedEventEnd - t.navigationStart); render_ready = timingFilter(t.domContentLoadedEventStart - t.domLoading); render_load = timingFilter(t.loadEventEnd - t.domLoading); total = timingFilter(t.loadEventEnd - t.navigationStart); } catch (error) { const msg = `[SV - getTiming]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return { wscreen, fscreen, network, network_prev, network_redirect, network_dns, network_tcp, network_request, network_response, network_interact, dom_loading, dom_interact, dom_ready, dom_load, dom_complete, js_ready, js_load, js_complete, render_ready, render_load, total }; } }; })(); const getSource = (function () { if (typeof window === 'undefined' || !window.performance) return notSupportPromisify; return async function (config) { const api_random = []; const api_timeout = []; const api_appoint = []; const source_random = []; const source_timeout = []; const source_appoint = []; const others = []; try { const { apiRatio = 0.1, sourceRatio = 0.1, apis = '', sources = '', timeout = 2000, whitelist = {}, paintTiming = false } = config || {}; const w_a = whitelist.api || ''; const w_s = whitelist.source || ''; const p = window.performance; const s = (p.getEntries && p.getEntries()) || []; const threshold = timeout; function sortSource(item) { const entryType = item.entryType || ''; const type = item.initiatorType || ''; const data = { name: item.name, duration: +Number.prototype.toFixed.call(item.duration, 2), type }; if (type === 'xmlhttprequest' || type === 'fetchrequest') { if (w_a) { if (isType('string')(w_a)) { if (data.name === w_a) return; } else if (isType('array')(w_a)) { const w_a_len = (w_a && w_a.length) || 0; for (let j = 0; j < w_a_len; j++) { if (data.name === w_a[j]) return; } } } randomRatio(apiRatio) && api_random.push(data); data.duration >= threshold && api_timeout.push(data); if (isType('string')(apis)) { data.name === apis && api_appoint.push(data); } else if (isType('array')(apis)) { apis.some(v => { if (v === data.name) { api_appoint.push(data); return true; } return false; }); } } else if (type === 'script' || type === 'css' || type === 'img' || type === 'link' || type === 'css') { if (w_s) { if (isType('string')(w_s)) { if (data.name === w_s) return; } else if (isType('array')(w_s)) { const w_s_len = (w_s && w_s.length) || 0; for (let m = 0; m < w_s_len; m++) { if (data.name === w_s[m]) return; } } } randomRatio(sourceRatio) && source_random.push(data); data.duration >= threshold && source_timeout.push(data); if (isType('string')(sources)) { type === sources && source_appoint.push(data); } else if (isType('array')(sources)) { sources.some(v => { if (v === type) { source_appoint.push(data); return true; } return false; }); } else if (isType('object')(sources)) { for (const k in sources) { if (k === type) { sources[k].some(v => { if (v === data.name) { source_appoint.push(data); return true; } return false; }); } } } } else { if (paintTiming && entryType === 'paint') { const { startTime } = item; let type = ''; if (data.name === 'first-paint' || data.name === 'first-contentful-paint') { switch (data.name) { case 'first-paint': type = 'FP'; break; case 'first-contentful-paint': type = 'FCP'; break; } others.push({ ...data, type, startTime }); } } } } if (compatCheck('generator')) { function* gen() { const len = s.length; for (let i = 0; i < len; i++) { sortSource(s[i]); yield; } } await timeslice(gen); } else { const len = s.length; for (let i = 0; i < len; i++) { sortSource(s[i]); } } } catch (error) { const msg = `[SV - getSource]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return { api_random, api_timeout, api_appoint, source_random, source_timeout, source_appoint, others }; } }; })(); const getExecTiming = (function () { if (typeof window === 'undefined' || !window.performance) return notSupportPromisify; return async function () { const exec = []; try { const p = window.performance; let measures = []; if (p.mark && p.measure) { measures = (p.getEntriesByType && p.getEntriesByType('measure')) || (p.getEntries && p.getEntries()) || []; } else { measures = measureCache; } function sortExec(item) { if (item.entryType === 'measure') { exec.push({ name: item.name, duration: +Number.prototype.toFixed.call(item.duration, 3) }); } } if (compatCheck('generator')) { function* gen() { const len = (measures && measures.length) || 0; for (let i = 0; i < len; i++) { sortExec(measures[i]); } yield; } await timeslice(gen); } else { const len = (measures && measures.length) || 0; for (let i = 0; i < len; i++) { sortExec(measures[i]); } } } catch (error) { const msg = `[SV - getExecTiming]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return { exec }; } }; })(); const getPerformanceData = (function () { if (typeof window === 'undefined' || !window.performance) return notSupportPromisify; return async function (config) { let memory, timings, sources, execTiming; try { const memo = getMemory(); memory = memo && memo.memory; timings = getTiming() || {}; sources = await getSource(config).then(data => data); execTiming = await getExecTiming().then(data => data); } catch (error) { const msg = `[SV - getPerformanceData]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return { memory: memory || 'N/A', ...timings, ...sources, ...execTiming }; } }; })(); const mark = (function () { if (typeof window === 'undefined' || !window.performance) return notSupport; return function (tag) { let res = true; try { const p = window.performance; const tagS = `${tag}_start`; const tagE = `${tag}_end`; if (!~marks.indexOf(tag)) { if (p.mark) { p.mark(tagS); } else { compatibleMark(tagS); } marks.push(tag); } else if (!~measures.indexOf(tag)) { if (p.mark && p.measure) { p.mark(tagE); p.measure(`${tag}`, tagS, tagE); } else { compatibleMark(tagE); compatibleMeasure(`${tag}`, tagS, tagE); } measures.push(tag); } else { res = false; console.warn(`Cannot repeat tag the mark: ${tag}`); } } catch (error) { res = false; const msg = `[SV - mark]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return res; } }; })(); const clearPerformance = (function () { if (typeof window === 'undefined' || !window.performance) return notSupport; return function (clearType) { try { const isClearSource = !clearType || clearType === 'source' || clearType === 'all'; const isClearMark = !clearType || clearType === 'mark' || clearType === 'all'; const isClearMeasure = !clearType || clearType === 'measure' || clearType === 'all'; const p = window.performance; if (isClearMark) { p.clearMarks && p.clearMarks(); marks.splice(0); markCache.splice(0); } if (isClearMeasure) { p.clearMeasures && p.clearMeasures(); measures.splice(0); measureCache.splice(0); } isClearSource && p.clearResourceTimings && p.clearResourceTimings(); return true; } catch (error) { const msg = `[SV - clearPerformance]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); return false; } }; })(); const observeSource = (function () { if (typeof window === 'undefined' || !window.performance) return notSupport; return function (target, callback, option) { let { sourceType = 'img', timeout = 2000, whitelist = {} } = option || {}; sourceType = isType('string')(sourceType) ? [sourceType.toLowerCase()] : [...sourceType.map(type => type.toLowerCase())]; getSourceByDom(target); const observer = new Observer(target, async function (mutationRecords) { let spendTime = 0; let frequence = 200; const len = (mutationRecords && mutationRecords.length) || 0; const sourceAddr = {}; for (let i = 0; i < len; i++) { const item = mutationRecords[i]; const recordType = item.type; switch (recordType) { case 'childList': const addNodes = item.addedNodes; await timeslice(iterationDOM(addNodes, sourceAddr)); break; case 'attributes': const attrName = item.attributeName; const target = item.target; const type = target.tagName && target.tagName.toLowerCase() || 'img'; if (attrName) { !sourceAddr[type] && (sourceAddr[type] = []); sourceAddr[type].push(target[attrName]); } break; } } timerQuery(); function timerQuery() { setTimeout(async function () { const sourceData = await getSource({ apiRatio: 0, sourceRatio: 0, sources: sourceAddr, whitelist }).then(data => data.source_appoint); let addrLen = 0; for (const k in sourceAddr) { addrLen += sourceAddr[k].length; } if ((sourceData && sourceData.length === addrLen) || spendTime >= timeout) { return callback && callback(sourceData); } else { spendTime += frequence; timerQuery(); } }, frequence); } }); async function getSourceByDom(dom, isAsync) { let data = []; try { const type = dom.nodeName.toLowerCase(); const sourceAddr = {}; if (~sourceType.indexOf(type)) { const sourceSrc = dom.src || dom.href || ''; !sourceAddr[type] && (sourceAddr[type] = []); sourceSrc && sourceAddr[type].push(sourceSrc); } const doms = dom.children; if (doms && doms.length > 0) { await timeslice(iterationDOM(doms, sourceAddr)); } data = await getSource({ apiRatio: 0, sourceRatio: 0, sources: sourceAddr, whitelist }).then(data => data.source_appoint); !isAsync && callback && callback(data); } catch (error) { const msg = `[SV - observeSource_getSourceByDom]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } finally { return data; } } function iterationDOM(doms, sourceAddr) { function sortDOM(doms, sourceStore) { const len = (doms && doms.length) || 0; for (let i = 0; i < len; i++) { const dom = doms[i]; const type = dom.nodeName.toLowerCase(); if (~sourceType.indexOf(type)) { const sourceSrc = dom.src || dom.href || ''; !sourceStore[type] && (sourceStore[type] = []); sourceSrc && sourceStore[type].push(sourceSrc); } const children = dom.children; const childLen = (children && children.length) || 0; if (childLen > 0) { sortDOM(children, sourceStore); } } } if (compatCheck('generator')) { return function* () { try { const len = (doms && doms.length) || 0; for (let i = 0; i < len; i++) { const dom = doms[i]; const type = dom.nodeName.toLowerCase(); if (~sourceType.indexOf(type)) { const sourceSrc = dom.src || dom.href || ''; !sourceAddr[type] && (sourceAddr[type] = []); sourceSrc && sourceAddr[type].push(sourceSrc); } const children = dom.children; const childLen = (children && children.length) || 0; if (childLen > 0) { yield timeslice(iterationDOM(children, sourceAddr)); } yield; } } catch (error) { const msg = `[SV - observeSource_iterationDOM]: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`; catchError('js', msg); } }; } else { sortDOM(doms, sourceAddr); } } return observer; }; })(); function compatibleMark(tag) { const ts = +((window.performance.now && ('' + window.performance.now())) || getTs()); markCache.push({ tag, ts }); } function compatibleMeasure(tag, tagStart, tagEnd) { const len = (markCache && markCache.length) || 0; let startTime = 0; let endTime = 0; for (let i = 0; i < len; i++) { const item = markCache[i]; if (item.tag === tagStart) startTime = item.ts; if (item.tag === tagEnd) endTime = item.ts; } measureCache.push({ entryType: 'measure', name: tag, duration: Math.abs(endTime - startTime) }); } function timingFilter(timing) { if (timing === null || timing === undefined || isNaN(timing)) return 'N/A'; return timing > 0 ? timing : 0; } function randomRatio(ratio) { if (ratio === 0) return false; if (Math.random() <= ratio) return true; return false; } class SV { constructor(config) { this.config = config; } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } getMemory() { return getMemory(); } getTiming() { return getTiming(); } async getSource() { return await getSource(this.config); } async getExecTiming() { return await getExecTiming(); } async getPerformanceData() { return getPerformanceData(this.config); } clearPerformance(clearType) { return clearPerformance(clearType); } observeSource(target, callback, option) { if (this.config && this.config.timeout) { option = { timeout: this.config.timeout, ...option }; } return observeSource(target, callback, option); } }const getEnvInfo = (function () { if (typeof window === 'undefined') return function () { console.warn('Please call in browser environment'); return false; }; return function () { const ua = navigator.userAgent; const scr = window.screen; const page_url = location.href; const referer = document.referrer; const info = { ts: getTs(), os: '', browser: '', screen_size: (scr.width || 0) + 'x' + (scr.height || 0), page_url, referer, device: ('ontouchstart' in window ? 'mobile' : 'pc'), ua }; const { isMobile, isAndroid, isIOS, isWindows, isMac, isWeixin, isSinaWeibo, isQQWeibo, isFacebook, isTwitter, isInstagram, isChrome, isFirefox, isGecko, isIE, isEdge, isOpera, isSafari, is360se, isQQ, isUCBrowser, isBaiduBrowser, isSougouBrowser, isLiebaoBrowser, isWebKit } = getUA(ua); if (isMobile) { if (isAndroid) { info.os = 'android'; }