use-monitor
Version:
use-monitor是轻量级前端埋点监控上报sdk
522 lines (508 loc) • 18 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.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;
}));