UNPKG

use-monitor

Version:

use-monitor是轻量级前端埋点监控上报sdk

522 lines (508 loc) 18 kB
(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.Monitor = factory()); })(this, (function () { 'use strict'; function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } //版本 var MonitorConfig; (function (MonitorConfig) { MonitorConfig["version"] = "1.0.0"; })(MonitorConfig || (MonitorConfig = {})); /** * 添加history原生并没有支持的pushState和replaceState的事件监听 * @param type * @returns */ var bindHistoryEvent = function bindHistoryEvent(type) { var historyEvent = history[type]; return function () { // eslint-disable-next-line prefer-rest-params var newEvent = historyEvent.apply(this, arguments); //执行history函数 var e = new Event(type) //声明自定义事件 ; e.arguments = arguments; window.dispatchEvent(e); //抛出事件 return newEvent; //返回方法,用于重写history的方法 }; }; //我司只有dom的click事件上报 可以在config的domEventList字段加上 var mouseEventList = ['click' // 'dblclick', // 'contextmenu', // 'mousedown', // 'mouseup', // 'mouseenter', // 'mouseout', // 'mouseover' ]; var pageEventList = ['page', 'hashchange', 'popstate', 'pushState', 'replaceState', 'performance']; function isHasSendBeacon() { return navigator.sendBeacon && typeof navigator.sendBeacon === 'function'; } var isServerRendering = function () { try { return !(typeof window !== 'undefined' && document !== undefined); } catch (e) { return true; } }(); var NOOP = function NOOP() { return undefined; }; var on = function () { if (isServerRendering) { return NOOP; } return function (element, event, handler) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; element.addEventListener(event, handler, options); }; }(); var delEmptyQueryNodes = function delEmptyQueryNodes(obj) { Object.keys(obj).forEach(function (key) { var value = obj[key]; value && _typeof(value) === 'object' && delEmptyQueryNodes(value); (value === '' || value === null || value === undefined || value.length === 0 || Object.keys(value).length === 0) && delete obj[key]; }); return obj; }; var stringifyPrimitive = function stringifyPrimitive(v) { switch (_typeof(v)) { case 'string': return v; case 'boolean': return v ? 'true' : 'false'; case 'number': return isFinite(v) ? v : ''; default: return ''; } }; /** * * @param obj * @param sep * @param eq * @param name * @returns */ var qsStringify = function qsStringify(obj, sep, eq, name) { sep = sep || '&'; eq = eq || '='; if (obj === null) { obj = undefined; } if (_typeof(obj) === 'object') { return Object.keys(obj).map(function (k) { var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; if (Array.isArray(obj[k])) { return obj[k].map(function (v) { return ks + encodeURIComponent(stringifyPrimitive(v)); }).join(sep); } return ks + encodeURIComponent(stringifyPrimitive(obj[k])); }).filter(Boolean).join(sep); } if (!name) return ''; return encodeURIComponent(stringifyPrimitive(name)) + eq + encodeURIComponent(stringifyPrimitive(obj)); }; /** * 通过attrKey查找父级元素 * @param {htmlElement} elem * @param {string} attrKey * @returns */ function getParentsByAttrKey(elem, attrKey) { var matched = []; while ((elem = elem['parentNode']) && elem.nodeType !== 9) { if (elem.nodeType === 1 && elem.getAttribute(attrKey)) { matched.push(elem); } } return matched; } /** * 获取Monitor路径 * @param {htmlElement Array} elements */ function getMonitorPaths() { var elements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; var res = []; elements.forEach(function (el) { var str = el.getAttribute('m_p') || ''; var splitStrs = str.split('|'); res.push.apply(res, _toConsumableArray(splitStrs.reverse())); }); return res.filter(function (str) { return !!str; }).reverse(); } function performanceMonitor() { var _this = this; document.addEventListener('DOMContentLoaded', function () { if (!performance) { // 当前浏览器不支持 console.log('你的浏览器不支持 performance 接口'); return; } var timing = performance.timing; console.log('PerformanceNavigationTiming~~~', window.PerformanceNavigationTiming); var start = timing.navigationStart; var dnsTime = 0, tcpTime = 0, firstPaintTime = 0, domRenderTime = 0; dnsTime = timing.domainLookupEnd - timing.domainLookupStart; tcpTime = timing.connectEnd - timing.connectStart; console.log('dnsTime~~ ', dnsTime); console.log('tcpTime~~ ', tcpTime); firstPaintTime = timing.responseStart - start; domRenderTime = timing.domContentLoadedEventEnd - start; // dom渲染 _this.push({ type: 'performance', config: { performance_time: domRenderTime, performance_type: 'page_dom_ready_time' } }); // 首屏时间 _this.push({ type: 'performance', config: { performance_type: 'page_first_paint_time', performance_time: firstPaintTime } }); }); } function errorEvent() { var _this = this; window.addEventListener('error', function (e) { var target = e.srcElement || e.target; var error_url = (target === null || target === void 0 ? void 0 : target.src) || (target === null || target === void 0 ? void 0 : target.href); _this.push({ type: 'error', config: { error_url: error_url, error_message: e.message } }); }, true); } function promiseReject() { var _this2 = this; window.addEventListener('unhandledrejection', function (event) { event.promise["catch"](function (error) { _this2.push({ type: 'reject', config: { error_message: error } }); }); }); } var Monitor = /*#__PURE__*/function () { function Monitor(options) { _classCallCheck(this, Monitor); this.requestOptions = {}; this.defaultOptions = this.initDef(options); this.installMonitor(); } return _createClass(Monitor, [{ key: "setToken", value: function setToken(token) { this.defaultOptions.token = token; } //初始化配置参数,请求参数,版本号 }, { key: "initDef", value: function initDef(options) { this.version = MonitorConfig.version; this.setRequestOptions(options); //添加pushState replaceState event window.history['pushState'] = bindHistoryEvent('pushState'); window.history['replaceState'] = bindHistoryEvent('replaceState'); return Object.assign({ sdkVersion: this.version, historyTracker: false, hashTracker: false, beaconTracker: false, pushPerformance: false, domTracker: true, debug: false, jsErrorTracker: true }, options); } }, { key: "setRequestOptions", value: function setRequestOptions(options) { this.requestOptions = Object.assign(Object.assign(Object.assign({}, this.requestOptions), { app_id: options === null || options === void 0 ? void 0 : options.app_id, app_name: options === null || options === void 0 ? void 0 : options.app_name, token: options === null || options === void 0 ? void 0 : options.token, module: options === null || options === void 0 ? void 0 : options.module, module_name: options === null || options === void 0 ? void 0 : options.module_name }), options === null || options === void 0 ? void 0 : options.config // 自定义参数 如 uuid之类的用户鉴权参数 ); } //设置 }, { key: "setConfig", value: function setConfig(config) { this.defaultOptions = Object.assign(Object.assign({}, this.defaultOptions), config); this.setRequestOptions(this.defaultOptions); } }, { key: "getCurrInfo", value: function getCurrInfo() { return Object.assign(Object.assign({}, this.requestOptions), { ua: navigator.userAgent, url: window.location.href, domain: document.domain || '', title: document.title, referrer: document.referrer || '' }); } // 打印日志到控制台 }, { key: "pushDebuggerLog", value: function pushDebuggerLog(data) { if (!this.defaultOptions.debug) { return; } console.log("----------".concat(data.type, "------------")); if (pageEventList.indexOf(data.type) !== -1) { console.log(data.app_name, ' ', data.module_name); } else { console.log(data.app_name || data.app_name, ' ', data.module_name || data.module_name, ' ', data.path_name, ' ', data.event_name); } } //pv自动上报 }, { key: "captureEvents", value: function captureEvents(eventList) { var _this = this; var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.requestOptions; eventList.forEach(function (event) { on(window, event, function () { _this.push(Object.assign(Object.assign({}, data), { type: event })); }); }); } // Dom 事件自动上报 }, { key: "targetKeyReport", value: function targetKeyReport(eventList) { var _this2 = this; eventList.forEach(function (event) { on(window, event, function (e) { var target = e.target; _this2.clickTarget(target); }); }); } }, { key: "clickTarget", value: function clickTarget(target) { var _this3 = this; var _a; var targetPushFn = function targetPushFn(target) { var parents = getParentsByAttrKey(target, 'm_p'); parents.unshift(target); var monitorPaths = getMonitorPaths(parents); var targetText = target.getAttribute('m_btn') || target.innerText; var targetValue = target.getAttribute('m_val') || ''; _this3.push({ type: 'click', path_name: JSON.stringify(monitorPaths), event_name: targetText, event_value: targetValue, actions: JSON.stringify([{ name: targetText }]) }); }; var mBtnsDomArr = []; // 根据属性自动发起点击埋点 if ((_a = target === null || target === void 0 ? void 0 : target.attributes) === null || _a === void 0 ? void 0 : _a.getNamedItem('m_btn')) { mBtnsDomArr.push(target); } var targetParentsBtns = getParentsByAttrKey(target, 'm_btn'); mBtnsDomArr = [].concat(_toConsumableArray(mBtnsDomArr), _toConsumableArray(targetParentsBtns)); mBtnsDomArr.forEach(function (e) { return targetPushFn(e); }); } }, { key: "clickPush", value: function clickPush() { var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.requestOptions; this.push({ type: 'click', path: data.path, actions: JSON.stringify([{ name: data.name || '', value: data.value || '' }]) }); } /** * 手动上报数据 * @param {object} obj */ }, { key: "push", value: function push(data) { if (!data.path_name) { data.path_name = ''; if (data.path) { if (Array.isArray(data.path)) { data.path_name = JSON.stringify(data.path); } else { data.path_name = data.path; } } } if (!data.event_name && data.actions) { var actions = JSON.parse(data.actions); if (actions.length) { data.event_name = actions[0].name || ''; data.event_value = actions[0].value || ''; } } this.reportTracker(data); } // 上报 }, { key: "reportTracker", value: function reportTracker(data) { var requestUrl = this.defaultOptions.requestUrl || this.defaultOptions.url; var params = Object.assign(this.getCurrInfo(), data, { time: new Date().getTime() }, { actions: JSON.stringify(data.actions) }, data === null || data === void 0 ? void 0 : data.config); params === null || params === void 0 ? true : delete params.config; var _params = delEmptyQueryNodes(params); this.pushDebuggerLog(_params); if (this.defaultOptions.beaconTracker && !!isHasSendBeacon) { this.requestByPost(requestUrl, _params); return; } this.requestByGet(requestUrl, _params); } // navigator.sendBeacon 关闭浏览器还能请求 }, { key: "requestByPost", value: function requestByPost(requestUrl, data) { var headers = { type: 'application/x-www-form-urlencoded' }; var blob = new Blob([JSON.stringify(data)], headers); navigator.sendBeacon(requestUrl, blob); } }, { key: "requestByGet", value: function requestByGet(url, data) { var img = document.createElement('img'); img.src = "".concat(url, "?_t=").concat(+new Date(), "&").concat(qsStringify(data)); img.style.display = 'none'; document.body.appendChild(img); img.parentNode.removeChild(img); } }, { key: "installMonitor", value: function installMonitor() { var _this$defaultOptions = this.defaultOptions, historyTracker = _this$defaultOptions.historyTracker, hashTracker = _this$defaultOptions.hashTracker, jsErrorTracker = _this$defaultOptions.jsErrorTracker, pushPerformance = _this$defaultOptions.pushPerformance, domTracker = _this$defaultOptions.domTracker; if (historyTracker) { this.captureEvents(['pushState', 'replaceState', 'popstate']); } if (hashTracker) { this.captureEvents(['hashchange']); } if (domTracker) { var domEventList = this.defaultOptions.domEventList; var eventList = (domEventList === null || domEventList === void 0 ? void 0 : domEventList.length) ? domEventList : mouseEventList; this.targetKeyReport(eventList); } if (pushPerformance) { performanceMonitor.call(this); } if (jsErrorTracker) { errorEvent.call(this); promiseReject.call(this); } } }]); }(); return Monitor; }));