UNPKG

yux-track-report

Version:

Buried point SDK for YUX

1,740 lines (1,739 loc) 62.4 kB
"use strict"; const DEFAULT_CONFIG = { // 开启debug "debug": false, // 上报地址 "reportUrl": "http://localhost:4545/", // 初始化钩子 "onLoad": function() { }, // 缓存配置 "bufferConfig": { // 存储方式 localStorage || cookie "type": "localStorage", // 存储名称 "name": "", // 关闭存储功能 "disable": false, // cookie存储时,采用安全的存储方式,即: //当secure属性设置为true时,cookie只有在https协议下才能上传 "cookie_secure": false, // cookie存储时,跨子域配置 "cookie_cross_subdomain": false, // cookie存储时,过期时间 "cookie_expiration": 1e3 }, // 禁止上报的时间名称 字符串数组 "disabledEvent": [], // 上报数据实现形式 beacon,post, get, img "trackType": "beacon", // 是否开启加密 "encrypt": false, // 单页面应用配置 "SPA": { // 开启SPA配置 "open": false, // SPA 实现类型,hash || history "mode": "hash" }, // PV指标自动触发配置 "pageAuto": false, // 上报数据前,每个字段长度截取配置,默认不截取 "truncateLength": -1, // 会话超时时长,单位分钟 "sessionIntervalMins": 30, // 开启上报失败滞留(后续请求中重试一次) "retention": false, // 是否开启自定义会话上报 "builtinSession": false, // 是否上报内置数据 "useBuiltinData": false, // 自动上报dom埋点开关 "autoTR": false, // 自动上报dom class name 作为检索标识 "autoClass": "auto-tr", "autoClickClass": "auto-click-tr", // 自动上报类型 every|once "autoType": "every", // 仅执行一次自动上报dom class name 作为检索标识, autoType 为 every时依旧生效 "autoOnceClass": "auto-once", // 自定义数据采集 "autoParamHook": () => { }, // 点击事件冒泡查找停止的标识,也可作业务模块分区 "bubbleStopFlag": "l1", // 曝光事件冒泡查找停止的标识的前缀,用于区分、清洗数据。如不需要则传'',寻找点击事件冒泡查找停止的标识 "bubbleStopFlagPrefixForShow": "show", // 发送前Hook "onBeforeSend": (url, data) => false, // 是否保留之前的事件数据 "useRetainPreviousEvent": false, // 保存到本地的key,同时也供后续上报使用,如e1、e2 // Array长度决定了保留多少条事件数据 "saveLocalRetainPEKeys": [], // 使用保留数据的哪些字段,可以是字符串或者正则 // 例如配置['eid'],上报数据为e1:{eid:xxx} "employRetainPEDataKeys": [] }; const LIB_CONFIG = { VERSION: "0.1.3", DEBUG: false }; const SYSTEM_EVENT_TYPE = "se"; const BUSINESS_EVENT_TYPE = "be"; const SYSTEM_EVENT_OBJECT = { // 会话开始事件 "tr_session_start": { "data_type": SYSTEM_EVENT_TYPE }, // 会话结束事件 "tr_session_close": { "data_type": SYSTEM_EVENT_TYPE }, // PV事件 "tr_pv": { "data_type": SYSTEM_EVENT_TYPE }, // 用户首次访问网站事件 "tr_activate": { "data_type": SYSTEM_EVENT_TYPE } }; const utf8Encode = function(string) { string = (string + "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); var utftext = "", start, end; var stringl = 0, n; start = end = 0; stringl = string.length; for (n = 0; n < stringl; n++) { var c1 = string.charCodeAt(n); var enc = null; if (c1 < 128) { end++; } else if (c1 > 127 && c1 < 2048) { enc = String.fromCharCode(c1 >> 6 | 192, c1 & 63 | 128); } else { enc = String.fromCharCode(c1 >> 12 | 224, c1 >> 6 & 63 | 128, c1 & 63 | 128); } if (enc !== null) { if (end > start) { utftext += string.substring(start, end); } utftext += enc; start = end = n + 1; } } if (end > start) { utftext += string.substring(start, string.length); } return utftext; }; const base64Encode = function(data) { var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "", tmp_arr = []; if (!data) { return data; } data = utf8Encode(data); do { o1 = data.charCodeAt(i++); o2 = data.charCodeAt(i++); o3 = data.charCodeAt(i++); bits = o1 << 16 | o2 << 8 | o3; h1 = bits >> 18 & 63; h2 = bits >> 12 & 63; h3 = bits >> 6 & 63; h4 = bits & 63; tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); } while (i < data.length); enc = tmp_arr.join(""); switch (data.length % 3) { case 1: enc = enc.slice(0, -2) + "=="; break; case 2: enc = enc.slice(0, -1) + "="; break; } return enc; }; let win; if (typeof window === "undefined") { win = { navigator: { userAgent: "" }, location: { pathname: "", href: "" }, document: { URL: "", referrer: "" }, screen: { width: "", height: "" }, console: "" }; } else { win = window; } const windowConsole = win.console; const console$1 = { log: function() { if (LIB_CONFIG.DEBUG && !common.isUndefined(windowConsole) && windowConsole) { try { windowConsole.log.apply(windowConsole, arguments); } catch (err) { common.each(arguments, function(arg) { windowConsole.log(arg); }); } } }, error: function() { if (LIB_CONFIG.DEBUG && !common.isUndefined(windowConsole) && windowConsole) { var args = ["TR error:"].concat(common.toArray(arguments)); try { windowConsole.error.apply(windowConsole, args); } catch (err) { common.each(args, function(arg) { windowConsole.error(arg); }); } } } }; const breaker$1 = {}; const common = { each(obj, iterator, context) { if (obj === null || obj === void 0) { return; } if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { obj.forEach(iterator, context); } else { for (let key in obj) { if (obj.hasOwnProperty.call(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker$1) { return; } } } } }, extend(obj) { common.each(Array.prototype.slice.call(arguments, 1), function(source) { for (let prop in source) { if (source[prop] !== void 0) { obj[prop] = source[prop]; } } }); return obj; }, isNumber(obj) { return Object.prototype.toString.call(obj) == "[object Number]"; }, isString(str) { return Object.prototype.toString.call(str) == "[object String]"; }, isRegExp(str) { return Object.prototype.toString.call(str) === "[object RegExp]"; }, isObject(obj) { return obj === Object(obj) && !common.isArray(obj); }, isArray(obj) { return Object.prototype.toString.apply(obj) === "[object Array]"; }, isUndefined(obj) { return obj === void 0; }, isArguments(obj) { return !!(obj && hasOwnProperty.call(obj, "callee")); }, isFunction(fn) { let bool = false; if (typeof fn === "function") { bool = true; } return bool; }, toArray(iterable) { if (!iterable) { return []; } if (iterable.toArray) { return iterable.toArray(); } if (common.isArray(iterable)) { return Array.prototype.slice.call(iterable); } if (common.isArguments(iterable)) { return Array.prototype.slice.call(iterable); } return common.values(iterable); }, values(obj) { var results = []; if (obj === null) { return results; } common.each(obj, function(value) { results[results.length] = value; }); return results; }, // 转化成json JSONDecode(string) { try { return JSON.parse(string); } catch (error) { return {}; } }, // json转化为string JSONEncode(json) { try { return JSON.stringify(json); } catch (error) { return ""; } }, encodeData(str) { return base64Encode(str); }, // 对象的字段值截取 truncate(obj, length) { let ret; if (typeof obj === "string") { ret = obj.slice(0, length); } else if (common.isArray(obj)) { ret = []; common.each(obj, function(val) { ret.push(common.truncate(val, length)); }); } else if (common.isObject(obj)) { ret = {}; common.each(obj, function(val, key) { ret[key] = common.truncate(val, length); }); } else { ret = obj; } return ret; }, generateQuery(formdata, arg_separator) { let use_val, use_key, tmp_arr = []; if (common.isUndefined(arg_separator)) { arg_separator = "&"; } common.each(formdata, function(val, key) { use_val = encodeURIComponent(val.toString()); use_key = encodeURIComponent(key); tmp_arr[tmp_arr.length] = use_key + "=" + use_val; }); return tmp_arr.join(arg_separator); }, // 删除左右两端的空格 trim(str) { if (!str) return; return str.replace(/(^\s*)|(\s*$)/g, ""); }, // 验证yyyy-MM-dd日期格式 checkTime(timeString) { const reg = /^(\d{4})-(\d{2})-(\d{2})$/; if (timeString) { if (!reg.test(timeString)) { return false; } else { return true; } } else { return false; } }, // 返回指定url的域名 // 若不传入url,返回当前网页的域名 getHost(url) { let host = ""; if (!url) { url = document.URL; } const regex = /.*\:\/\/([^\/]*).*/; const match = url.match(regex); if (match) { host = match[1]; } return host; }, // 获取url上指定参数的值 getQueryParam(url, param) { const target = param.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); const regexS = "[\\?&]" + target + "=([^&#]*)"; const regex = new RegExp(regexS); const results = regex.exec(url); if (results === null || results && typeof results[1] !== "string" && results[1].length) { return ""; } else { return decodeURIComponent(results[1]).replace(/\+/g, " "); } }, // 删除对象中空字段 deleteEmptyProperty(obj) { if (!this.isObject(obj)) { return; } for (let key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] === null || this.isUndefined(obj[key]) || obj[key] === "") { delete obj[key]; } } } return obj; } }; common.register_event = function() { var register_event = function(element, type, handler, oldSchool, useCapture) { if (!element) { console$1.error("No valid element provided to register_event"); return; } if (element.addEventListener && !oldSchool) { element.addEventListener(type, handler, !!useCapture); } else { var ontype = "on" + type; var old_handler = element[ontype]; element[ontype] = makeHandler(element, handler, old_handler); } }; function makeHandler(element, new_handler, old_handlers) { var handler = function(event) { event = event || fixEvent(window.event); if (!event) { return void 0; } var ret = true; var old_result, new_result; if (common.isFunction(old_handlers)) { old_result = old_handlers(event); } new_result = new_handler.call(element, event); if (false === old_result || false === new_result) { ret = false; } return ret; }; return handler; } function fixEvent(event) { if (event) { event.preventDefault = fixEvent.preventDefault; event.stopPropagation = fixEvent.stopPropagation; } return event; } fixEvent.preventDefault = function() { this.returnValue = false; }; fixEvent.stopPropagation = function() { this.cancelBubble = true; }; return register_event; }(); common.register_hash_event = function(callback) { common.register_event(window, "hashchange", callback); }; common.info = { domain(referrer) { if (!referrer) return ""; const split = referrer.split("/"); if (split.length >= 3) { return split[2]; } return ""; }, properties() { return { // 浏览器UA browserUA: win.navigator.userAgent, // 页面URL currentUrl: document.URL, // 域名 currentDomain: this.domain(document.URL), // referrer 数据来源 referrer: win.document.referrer || "", // referrer 域名 referringDomain: this.domain(win.document.referrer), // 客户端分辨率 width screenWidth: win.screen.width, // 客户端分辨率 height screenHeight: win.screen.height }; } }; common.sendRequest = function(url, type, data, callback) { if (common.isFunction(window.navigator.sendBeacon) && type === "beacon") { url += "?" + common.generateQuery(data); window.navigator.sendBeacon(url); } else if (type === "img") { url += "?" + common.generateQuery(data); let img = document.createElement("img"); img.src = url; img.width = 1; img.height = 1; img.onload = function() { this.onload = null; if (common.isFunction(callback)) { callback({ status: 1 }); } }; img.onerror = function() { this.onerror = null; if (common.isFunction(callback)) { callback({ status: 0, error: true, message: "error" }); } }; img.onabort = function() { this.onabort = null; if (common.isFunction(callback)) { callback({ status: 0, error: true, message: "onabort" }); } }; } else if (type === "get") { url += "?" + common.generateQuery(data); common.ajax.get(url, callback); } else if (type === "post") { common.ajax.post(url, data, callback); } }; common.ajax = { post: function(url, options, callback, timeout) { var that = this; that.callback = callback || function(params) { }; try { var req = new XMLHttpRequest(); req.open("POST", url, true); req.setRequestHeader("Content-type", "application/json"); req.withCredentials = true; req.ontimeout = function() { that.callback({ status: 0, error: true, message: "request " + url + " time out" }); }; req.onreadystatechange = function() { if (req.readyState === 4) { if (req.status === 200) { that.callback(common.JSONDecode(req.responseText)); } else { var message = "Bad HTTP status: " + req.status + " " + req.statusText; that.callback({ status: 0, error: true, message }); } } }; req.timeout = timeout || 5e3; req.send(common.JSONEncode(options)); } catch (e) { } }, get: function(url, callback) { try { var req = new XMLHttpRequest(); req.open("GET", url, true); req.withCredentials = true; req.onreadystatechange = function() { if (req.readyState === 4) { if (req.status === 200) { if (callback) { callback(req.responseText); } } else { if (callback) { var message = "Bad HTTP status: " + req.status + " " + req.statusText; callback({ status: 0, error: true, message }); } } } }; req.send(null); } catch (e) { } } }; common.UUID = function() { var T = function() { var d = 1 * /* @__PURE__ */ new Date(), i = 0; while (d == 1 * /* @__PURE__ */ new Date()) { i++; } return d.toString(16) + i.toString(16); }; var R = function() { return Math.random().toString(16).replace(".", ""); }; var UA = function(n) { var ua = navigator.userAgent, i, ch, buffer = [], ret = 0; function xor(result, byte_array) { var j, tmp = 0; for (j = 0; j < byte_array.length; j++) { tmp |= buffer[j] << j * 8; } return result ^ tmp; } for (i = 0; i < ua.length; i++) { ch = ua.charCodeAt(i); buffer.unshift(ch & 255); if (buffer.length >= 4) { ret = xor(ret, buffer); buffer = []; } } if (buffer.length > 0) { ret = xor(ret, buffer); } return ret.toString(16); }; return function() { var se = String(screen.height * screen.width); if (se && /\d{5,}/.test(se)) { se = se.toString(16); } else { se = String(Math.random() * 31242).replace(".", "").slice(0, 8); } var val = T() + "-" + R() + "-" + UA() + "-" + se + "-" + T(); if (val) { return val; } else { return (String(Math.random()) + String(Math.random()) + String(Math.random())).slice(2, 15); } }; }(); common.innerEvent = { on: function(key, fn) { if (!this._list) { this._list = {}; } if (!this._list[key]) { this._list[key] = []; } this._list[key].push(fn); }, trigger: function() { var args = Array.prototype.slice.call(arguments); var key = args[0]; var arrFn = this._list && this._list[key]; if (!arrFn || arrFn.length === 0) { return; } for (var i = 0; i < arrFn.length; i++) { if (typeof arrFn[i] == "function") { arrFn[i].apply(this, args); } } } }; common.localStorage = { set: function(name, value) { try { window.localStorage.setItem(name, value); } catch (err) { common.localStorage.error(err); } }, get: function(name) { try { return window.localStorage.getItem(name); } catch (err) { common.localStorage.error(err); } return null; }, parse: function(name) { try { return common.JSONDecode(common.localStorage.get(name)) || {}; } catch (err) { console$1.error(err); } return null; }, remove: function(name) { try { window.localStorage.removeItem(name); } catch (err) { common.localStorage.error(err); } }, error: function(msg) { console$1.error("localStorage error: " + msg); } }; common.cookie = { set: function(name, value, isSecure, isCrossSubdomain, duration) { var _domain = "", expires = "", secure = ""; if (isSecure) { secure = "; secure"; } if (isCrossSubdomain) { var matches = document.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i), domain = matches ? matches[0] : ""; _domain = domain ? "; domain=." + domain : ""; } if (duration) { var date = /* @__PURE__ */ new Date(); date.setTime(date.getTime() + duration * 24 * 60 * 60 * 1e3); expires = "; expires=" + date.toGMTString(); } var new_cookie_val = name + "=" + encodeURIComponent(value) + expires + "; path=/" + _domain + secure; document.cookie = new_cookie_val; return new_cookie_val; }, get: function(name) { var nameEQ = name + "="; var ca = document.cookie.split(";"); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == " ") { c = c.substring(1, c.length); } if (c.indexOf(nameEQ) === 0) { return decodeURIComponent(c.substring(nameEQ.length, c.length)); } } return null; }, parse: function(name) { var cookie; try { cookie = common.JSONDecode(common.cookie.get(name)) || {}; } catch (err) { } return cookie; }, remove: function(name, isCrossSubdomain) { common.cookie.set(name, "", -1, false, isCrossSubdomain); } }; common.localStorage_supported = () => { let supported = true; try { let key = "__support__", val = "sdk"; common.localStorage.set(key, val); if (common.localStorage.get(key) !== val) { supported = false; } common.localStorage.remove(key); } catch (error) { supported = false; } if (!supported) { console$1.error("localStorage 不支持,自动退回到cookie存储方式"); } return supported; }; common.createClassReg = (className) => { return new RegExp("(^|\\s)" + className + "(\\s|$)"); }; common.autoTraverseDomUp = (el, autoReg, flagKey, autoParamHook, type, prefix) => { let flagValue = ""; let targetEle = null; const _autoParamsValues = {}; let _autoParamsKeys = []; let _directParamValues = {}; while (el && el.tagName !== "BODY") { if (autoReg.test(el.getAttribute("class"))) { _directParamValues = autoParamHook(el, type); _autoParamsKeys = Object.keys(_directParamValues || {}); targetEle = el; break; } el = el.parentNode; } if (!targetEle) return; while (targetEle && targetEle.tagName !== "BODY") { common.each(_autoParamsKeys, (key) => { if (targetEle.hasAttribute(`data-${key}`) && !_directParamValues[key] && !_autoParamsValues[key]) { _autoParamsValues[key] = targetEle.getAttribute(`data-${key}`); } }); if (flagKey && targetEle.hasAttribute(`data-${flagKey}`) && flagValue === "") { flagValue = targetEle.getAttribute(`data-${flagKey}`); break; } targetEle = targetEle.parentNode; } let _flagKey = flagKey; if (type === "exposure" && !!prefix) { _flagKey = _flagKey.replace(prefix, ""); } return common.extend({}, _directParamValues, _autoParamsValues, { [`${_flagKey}`]: flagValue }); }; class EventTrack { constructor(context) { this.context = context; this["buffer"] = this.context["buffer"]; this["buffer"].caching({ sessionStartTime: 0, updatedTime: 0 }); this["buffer"].caching({ sessionReferrer: document.referrer }); common.innerEvent.on("singlePage:change", (eventName, urlParams) => { this["buffer"].caching({ sessionReferrer: document.URL }); }); } /** * 判断是否为其它渠道 */ checkChannel() { const referrer = this.context.getProperty("sessionReferrer"); let is_other_channel = false; if (common.getHost(referrer) !== window.location.host) { is_other_channel = true; } return is_other_channel; } /** * TODO * 判断指定事件是否被禁止上报 * @param {String} eventName * @returns {Boolean} */ checkEventIsDisabled(eventName) { if (eventName in this.context["config"]["disabledEvent"]) { return true; } return false; } /** * 打开新会话 */ startNewSession() { this["buffer"].caching({ sessionUUID: common.UUID(), sessionStartTime: (/* @__PURE__ */ new Date()).getTime() }); this.track("tr_session_start"); } /** * 查询滞留消息并发送 */ checkRetentionList() { if (!this.context.getConfig("retention")) return; const retentionList = this["buffer"].getTargetData("Retention"); this["buffer"].removeLocalData("Retention"); if (common.isArray(retentionList) && retentionList.length > 0) { common.each(retentionList, (item) => { const id = item.builtinData.eventId; this.track(id, item); }); } } /** * 关闭当前会话 */ closeCurSession() { let time = (/* @__PURE__ */ new Date()).getTime() - 1e3; const sessionStartTime = this.context.getProperty("sessionStartTime"); const LASTEVENT = this.context.getProperty("LASTEVENT"); if (LASTEVENT && LASTEVENT.triggerTime) { time = LASTEVENT.triggerTime; } const sessionTotalLength = time - sessionStartTime; if (sessionTotalLength < 0) { return; } this.track("tr_session_close", { sessionCloseTime: time, sessionTotalLength }); } /** * 判断会话重新开启 * 判断条件:会话首次开始、指定的一段时间内用户无事件操作、其它渠道进来 */ checkSession(callback) { const now_date_time_ms = (/* @__PURE__ */ new Date()).getTime(); if (this.context.getConfig("builtinSession")) { const session_start_time = 1 * this.context.getProperty("sessionStartTime") / 1e3; const updated_time = 1 * this.context.getProperty("updatedTime") / 1e3; const now_date_time_se = 1 * now_date_time_ms / 1e3; const other_channel_Bool = this.checkChannel(); if (session_start_time === 0 || now_date_time_se > updated_time + 60 * this.context.getConfig("sessionIntervalMins") || other_channel_Bool) { if (session_start_time === 0) { this.startNewSession(); } else { this.closeCurSession(); this.startNewSession(); } } } this.checkRetentionList(); this["buffer"].caching({ updatedTime: now_date_time_ms }); if (common.isFunction(callback)) { callback(); } } /** * 设置一个指定事件的耗时监听器 * @param {String} eventName */ setConsumingTimeListener(eventName) { if (common.isUndefined(eventName)) { console$1.error("事件耗时监听器需要一个事件名称"); return; } if (this.checkEventIsDisabled(eventName)) { return; } this["buffer"].setConsumingTime(eventName, (/* @__PURE__ */ new Date()).getTime()); } /** * 发送PV事件,在此之前检测session * @param {Object} properties pv属性 * @param {*} callback */ trackPV(properties, callback) { this.checkSession(() => { this.track("tr_pv", common.extend({}, properties), callback); }); } /** * 追踪事件(上报用户事件触发数据) * @param {String} eventName 事件名称(必须) * @param {Object} properties 事件属性 * @param {Function} callback 上报后的回调方法 * @param {String} event_type 自定义事件类型 * @returns {Object} track_data 上报的数据 */ track(eventName, properties, callback, event_type) { if (common.isUndefined(eventName)) { console$1.error("上报数据需要一个事件名称"); return; } if (!common.isFunction(callback)) { callback = function() { }; } if (this.checkEventIsDisabled(eventName)) { callback({ status: 0 }); return; } this["buffer"].getLocalData(); properties = properties || {}; let user_set_properties = common.JSONDecode(common.JSONEncode(properties)) || {}; let costTime; const startListenTime = this["buffer"].removeEventTimer(eventName); if (!common.isUndefined(startListenTime)) { costTime = (/* @__PURE__ */ new Date()).getTime() - startListenTime; console$1.log("指定事件" + eventName + "共用时:" + costTime + "ms"); } let data_type = BUSINESS_EVENT_TYPE; if (event_type) { data_type = event_type; } else if (SYSTEM_EVENT_OBJECT[eventName]) { data_type = SYSTEM_EVENT_OBJECT[eventName].data_type; } let triggerTime = (/* @__PURE__ */ new Date()).getTime(); if (eventName === "tr_session_close") { triggerTime = properties.sessionCloseTime; console$1.log("上次会话已超出设定范围,会话共用时" + properties.sessionTotalLength + "ms"); delete user_set_properties["sessionCloseTime"]; delete user_set_properties["sessionTotalLength"]; } user_set_properties = common.extend({}, this.context.getProperty("customProperties"), user_set_properties); let builtinData = common.extend({}, { dataType: data_type, libVersion: LIB_CONFIG.VERSION, // 事件名称 eventId: eventName, // 事件触发时间 triggerTime, // 用户首次访问时间 persistedTime: this.context.getProperty("persistedTime"), // 客户端唯一凭证(设备凭证) deviceId: this.context.getDeviceId(), // 应用凭证 appId: this.context.getConfig("appId"), costTime, // 当前关闭的会话时长 sessionTotalLength: properties.sessionTotalLength, // 当前会话id sessionUUID: this.context.getProperty("sessionUUID"), // 基础信息 ...common.info.properties() }); let data = common.extend({}, { builtinData }, user_set_properties); if (data_type === BUSINESS_EVENT_TYPE) { if (this.checkChannel()) { this["buffer"].caching({ sessionReferrer: document.URL }); } } if (!this.context.getConfig("SPA").open) { if (["tr_activate", "tr_session_close"].indexOf(eventName) > 0) { this["buffer"].caching({ sessionReferrer: document.URL }); } } if (this.context.getConfig("SPA").open) { const sessionReferrer = this.context.getProperty("sessionReferrer"); if (sessionReferrer !== data["referrer"]) { data.builtinData["referrer"] = sessionReferrer; data.builtinData["referringDomain"] = common.info.domain(sessionReferrer); } } data.builtinData = common.JSONEncode(data.builtinData); const truncateLength = this.context.getConfig("truncateLength"); let truncated_data = data; if (common.isNumber(truncateLength) && truncateLength > 0) { truncated_data = common.truncate(data, truncateLength); } const callbackFn = (response) => { callback(response, data); if (response.status === 0 && this.context.getConfig("retention")) { const copyData = common.extend({}, data); copyData.builtinData = common.JSONDecode(copyData.builtinData); if (copyData.builtinData.eventId.indexOf("r_") === 0) { return; } let retentionList = this["buffer"].getTargetData("Retention"); if (!common.isArray(retentionList)) { retentionList = []; } copyData.builtinData.eventId = "r_" + copyData.builtinData.eventId; retentionList.push(copyData); this["buffer"].saveRetentionData("Retention", retentionList, 1); } }; const url = this.context.getConfig("reportUrl"); const trackType = this.context.getConfig("trackType"); const isEncrypt = this.context.getConfig("encrypt"); if (!this.context.getConfig("useBuiltinData") && truncated_data && truncated_data.hasOwnProperty("builtinData")) { delete truncated_data["builtinData"]; } const DATA = isEncrypt ? { data: common.encodeData(common.JSONEncode(truncated_data)), appId: this.context.getConfig("appId") } : { ...truncated_data, appId: this.context.getConfig("appId") }; const peKeys = this.context.getConfig("saveLocalRetainPEKeys"); if (this.context.getConfig("useRetainPreviousEvent") && common.isArray(peKeys) && peKeys.length > 0) { const peData = this["buffer"].getTargetData("PreviousEventData"); if (common.isObject(peData)) { const selectPEKeys = this.context.getConfig("employRetainPEDataKeys"); if (common.isArray(selectPEKeys)) { Object.keys(peData).forEach((key) => { const targetObj = {}; const curData = peData[key]; selectPEKeys.forEach((k) => { if (common.isRegExp(k)) { const reg = new RegExp(k); Object.keys(curData).forEach((ck) => { if (reg.test(ck) && !common.isUndefined(curData[ck])) { targetObj[ck] = curData[ck]; } }); } else if (common.isString(k) && !common.isUndefined(curData[k])) { targetObj[k] = curData[k]; } }); DATA[key] = common.JSONEncode(common.extend({}, targetObj)); }); } else { console$1.error("employRetainPEDataKeys should be an array"); } if (Object.keys(peData).length !== peKeys.length) { for (let i = 0; i < peKeys.length; i++) { const curKey = peKeys[i]; if (!peData[curKey]) { peData[curKey] = common.extend({}, DATA); break; } else { continue; } } } else { const len = peKeys.length; for (let i = 0; i < len - 1; i++) { peData[peKeys[i]] = common.extend({}, peData[peKeys[i + 1]]); } peData[peKeys[len - 1]] = common.extend({}, DATA); } this["buffer"].saveRetentionData("PreviousEventData", peData, 1); } } else { console$1.error("saveLocalRetainPEKeys should be an array"); } console$1.log("事件 " + eventName + " 上报的数据:", DATA); if (typeof this.context["config"]["onBeforeSend"] === "function") { const isStopSend = this.context["config"]["onBeforeSend"](DATA, common, callbackFn); if (isStopSend) { return; } } common.sendRequest( url, trackType, DATA, callbackFn ); if (["tr_session_start", "tr_session_close", "tr_activate"].indexOf(eventName) === -1) { this.checkSession(); } if (["tr_session_start", "tr_session_close"].indexOf(eventName) === -1) { this["buffer"].caching({ LASTEVENT: { eventId: eventName, triggerTime } }); } } } class Buffer { constructor(config) { const bufferConfig = config["bufferConfig"]; if (common.isObject(bufferConfig)) { this["name"] = bufferConfig["name"] || "track_report_" + config["appId"] + "_sdk"; let bufferType = bufferConfig["type"]; this.setDisabled(bufferConfig["disable"]); if (bufferType === "localStorage" && common.localStorage_supported()) { this["buffer"] = common.localStorage; } else { this["buffer"] = common.cookie; this.setCookieOptions(bufferConfig); } this.getLocalData(); this.save(); } else { console$1.error("buffer配置设置错误"); } } // 设置是否缓存,设置为否时清除缓存数据 setDisabled(disabled) { this.disabled = disabled; if (this.disabled) { this.removeLocalData(); } } // 加载本地存储信息 getLocalData() { const localData = this["buffer"].parse(this["name"]); if (localData) { this["props"] = common.extend({}, localData); } } // 获取本地指定数据 getTargetData(name) { return this["buffer"].parse(name); } // cookie 方式下配置 setCookieOptions(bufferConfig) { this.setCookieSecure(bufferConfig["cookie_secure"]); this.setCrossSubdomain(bufferConfig["cookie_cross_subdomain"]); this.defaultExpirationTime = this.expirationTime = bufferConfig["cookie_expiration"]; } /** * cookie 采用安全的方式存储数据 * 修改后,重新保存数据 * secure为true时,cookie只能用https协议上传 * @param {Boolean} isSecure */ setCookieSecure(isSecure) { if (isSecure !== this.isSecure) { this.isSecure = isSecure ? true : false; this.removeLocalData(); this.save(); } } /** * cookie存储方式下 跨子域设置 * @param {Boolean} isCrossSubdomain */ setCrossSubdomain(isCrossSubdomain) { if (isCrossSubdomain !== this.isCrossSubdomain) { this.isCrossSubdomain = isCrossSubdomain; this.removeLocalData(); this.save(); } } // 数据保存到本地 save(name) { if (this.disabled) { return; } this["buffer"].set( name || this["name"], common.JSONEncode(this["props"]), // cookie 下有效 this.isSecure, this.isCrossSubdomain, this.expirationTime ); } // 保存滞留数据 saveRetentionData(name, data, duration) { if (this.disabled) { return; } this["buffer"].set( name || this["name"], common.JSONEncode(data), // cookie 下有效 this.isSecure, this.isCrossSubdomain, typeof duration === "undefined" ? this.defaultExpirationTime : duration ); } // 移除本地数据 removeLocalData(name) { this["buffer"].remove(name || this.name, false); this["buffer"].remove(name || this.name, true); } /** * 缓存指定的数据,同时将该数据保存到本地 * @param {Object} props 数据 * @param {String} name 自定义名称 * @param {Number} duration 时长,单位天 * @returns {Boolean} 返回true表示成功 */ caching(props, name, duration) { if (common.isObject(props)) { this.expirationTime = typeof duration === "undefined" ? this.defaultExpirationTime : duration; common.extend(this["props"], props); this.save(typeof name === "undefined" ? this["name"] : name); return true; } return false; } /** * 清除指定的缓存数据 */ removeCachedData(prop) { if (prop in this["props"]) { delete this["props"][prop]; this.save(); } } /** * 事件计时器,记录用户触发指定事件需要的时间,同时保存到本地 * @param {String} eventName 事件名称 * @param {Date} timestamp 计时器开始时间戳 */ setConsumingTime(eventName, timestamp) { const timers = this["props"]["costTime"] || {}; timers[eventName] = timestamp; this["props"]["costTime"] = timers; this.save(); } /** * 清除指定事件的计时器 * @param {String} eventName 事件名称 * @returns {Date} 返回清除的时间戳 */ removeEventTimer(eventName) { const timers = this["props"]["costTime"] || {}; const timestamp = timers[eventName]; if (!common.isUndefined(timestamp)) { delete this["props"]["costTime"][eventName]; this.save(); } return timestamp; } } const getPath = () => { return location.pathname + location.search; }; const override = (history2, event, callFn) => { if (history2[event]) { const fn = history2[event]; history2[event] = function() { callFn.apply(this, arguments); fn.apply(this, arguments); }; } else { history2[event] = function() { callFn.apply(this, arguments); }; } }; const defaultConfig = { mode: "hash", callback: () => { } }; class SPA { constructor(props) { this.init(props); } // 初始化 init(config) { this.url = document.URL; this.path = getPath(); this.config = common.extend(defaultConfig, config || {}); this.bindEvent(); } // 判断模式,绑定时间 bindEvent() { if (this.config.mode === "history") { if (!history.pushState || !window.addEventListener) { return; } override(history, "pushState", this.overridePushState.bind(this)); override(history, "replaceState", this.overrideReplaceState.bind(this)); window.addEventListener("popstate", this.handlePopState.bind(this)); } else if (this.config.mode === "hash") { common.register_hash_event(this.handleHashState.bind(this)); } else { console$1.error("mode error"); return; } } overridePushState() { this.urlChangeHandler(true); } overrideReplaceState() { this.urlChangeHandler(false); } handlePopState() { this.urlChangeHandler(true); } handleHashState() { this.urlChangeHandler(true); } // 路由发生变化时做处理 urlChangeHandler(isHistoryChange) { setTimeout(() => { if (this.config.mode === "history") { const oldPath = this.path; const newPath = getPath(); if (oldPath != newPath && this.checkUrl(newPath, oldPath)) { this.path = newPath; if (isHistoryChange && common.isFunction(this.config.callback)) { this.config.callback.call(); common.innerEvent.trigger("singlePage:change", { oldUrl: this.url, nowUrl: document.URL }); this.url = document.URL; } } } else if (this.config.mode === "hash") { if (common.isFunction(this.config.callback)) { this.config.callback.call(); common.innerEvent.trigger("singlePage:change", { oldUrl: this.url, nowUrl: document.URL }); this.url = document.URL; } } }, 0); } checkUrl(newPath, oldPath) { return !!(newPath && oldPath); } } class PBD { constructor(props) { this.storage_name = "qd_page_browsing"; this.min_limit = 2e3; this.options = {}; this.page_id = null; this.url = location.href; this.start_time = +/* @__PURE__ */ new Date(); this.page_show_status = true; this.page_hidden_status = false; this.beat_time = 1e3; this.beat_timer = null; this.beat_count = 0; this.min_duration = 18e4; this.max_noBehavior = 6e4; this.isNoBehavior = false; this.behavior_timer = null; this.isWatchBeat = false; this.watchBeatTimer = null; this.watchBeatDuration = 0; this.watchBeatUpdate = null; this.watchBeatCount = 0; this.watchBeatDurationDone = null; this.isWatchBeatDurationDone = false; this.debug = false; this.init(props); } init(options) { if (options) { this.options = options; const min_limit = options.min_limit; if (min_limit && (this.isNumber(min_limit) || this.isNumber(min_limit * 1)) && min_limit * 1 > 0) { this.min_limit = min_limit; } const min_duration = options.min_duration; if (min_duration && (this.isNumber(min_duration) || this.isNumber(min_duration * 1)) && min_duration * 1 > 0) { this.min_duration = min_duration; } const max_noBehavior = options.max_noBehavior; if (max_noBehavior && (this.isNumber(max_noBehavior) || this.isNumber(max_noBehavior * 1)) && max_noBehavior * 1 > 0) { this.max_noBehavior = max_noBehavior; } this.isWatchBeat = options.isWatchBeat; const watchBeatDuration = options.watchBeatDuration; if (watchBeatDuration && (this.isNumber(watchBeatDuration) || this.isNumber(watchBeatDuration * 1)) && watchBeatDuration * 1 > 0) { this.watchBeatDuration = watchBeatDuration; } this.watchBeatUpdate = options.watchBeatUpdate; this.watchBeatDurationDone = options.watchBeatDurationDone; this.isLoopWatchBeat = options.isLoopWatchBeat; this.debug = options.debug; this.page_id = options.page_id; if (this.max_noBehavior >= this.watchBeatDuration && this.max_noBehavior % this.watchBeatDuration === 0) { this.max_noBehavior += 1e3; } } if (!this.isUrlListening(this.url)) { return; } this.addEventListener(); if (document.hidden === true) { this.page_show_status = false; } else { this.startBeat(); this.startWatchBeatTimer(); } } pageStartHandler() { this.start_time = +/* @__PURE__ */ new Date(); if (!document.hidden === true) { this.page_show_status = true; } else { this.page_show_status = false; } this.url = location.href; } pageHiddenHandler() { this.page_hidden_status = false; } pageEndHandler() { if (this.page_hidden_status === true) return; this.url = location.href; var data = this.getProps(); if (this.page_show_status === false) { delete data.$event_duration; } this.page_show_status = false; this.page_hidden_status = true; this.sendData(data); this.pageHiddenHandler(); this.removeBeatData(); } addEventListener() { this.addPageStartListener(); this.addPageSwitchListener(); this.addPageEndListener(); this.addBehaviorListener(); } addBehaviorListener() { var _this = this; if ("onscroll" in window) { window.addEventListener("scroll", _this.throttle(function() { if (_this.isNoBehavior) { _this.console("====滚动重启状态了", _this.isNoBehavior); _this.pageStartHandler(); _this.pageHiddenHandler(); _this.startBeat(); _this.startWatchBeatTimer(); _this.startBehaviorTimer(); } else { _this.console("====滚动"); _this.startBehaviorTimer(); } }, 1e3)); } _this.startBehaviorTimer(); } startBehaviorTimer() { var _this = this; _this.isNoBehavior = false; if (_this.behavior_timer) { _this.stopBehaviorTimer(); } _this.console("====开始行为监听倒计时了"); _this.behavior_timer = setTimeout(function() { _this.isNoBehavior = true; _this.pageEndHandler(); _this.stopBeat(); _this.stopWatchBeatTimer(); _this.stopBehaviorTimer(); _this.console("====超出行为监听时长,强制发送并停止了"); }, _this.max_noBehavior); } stopBehaviorTimer() { clearTimeout(this.behavior_timer); this.behavior_timer = null; } addPageStartListener() { var _this = this; if ("onpageshow" in window) { window.addEventListener("pageshow", function() { _this.pageStartHandler(); _this.pageHiddenHandler(); }); } } addPageEndListener() { var _this = this; this.each(["pagehide", "beforeunload", "unload"], function(key) { if ("on" + key in window) { window.addEventListener(key, function() { _this.pageEndHandler(); _this.stopBeat(); _this.stopWatchBeatTimer(); }); } }); } addPageSwitchListener() { var _this = this; _this.listenPageState({ visible: function() { _this.console("====可见了"); _this.pageStartHandler(); _this.pageHiddenHandler(); _this.startBeat(); _this.startWatchBeatTimer(); _this.startBehaviorTimer(); }, hidden: function() { _this.console("====不可见了"); _this.pageEndHandler(); _this.stopBeat(); _this.stopWatchBeatTimer(); _this.stopBehaviorTimer(); } }); } automaticHandler() { this.pageEndHandler(); this.stopBeat(); this.pageStartHandler(); this.pageHiddenHandler(); this.startBeat(); } isUrlListening(url) { if (typeof this.options.isUrlListening === "function") { if (typeof url === "string" && url !== "") { return this.options.isUrlListening(url); } else { return true; } } else if (typeof this.options.isUrlListening === "boolean") { return this.options.isUrlListening; } else { return true; } } listenPageState(obj) { var _this = this; var visibilityStore = { visibleHandler: _this.isFunction(obj.visible) ? obj.visible : function() { }, hiddenHandler: _this.isFunction(obj.hidden) ? obj.hidden : function() { }, visibilityChange: null, hidden: null, isSupport: function() { return typeof document[this.hidden] !== "undefined"; }, init: function() { if (typeof document.hidden !== "undefined") { this.hidden = "hidden"; this.visibilityChange = "visibilitychange"; } else if (typeof document.mozHidden !== "undefined") { this.hidden = "mozHidden"; this.visibilityChange = "mozvisibilitychange"; } else if (typeof document.msHidden !== "undefined") { this.hidden = "msHidden"; this.visibilityChange = "msvisibilitychange"; } else if (typeof document.webkitHidden !== "undefined") { this.hidden = "webkitHidden"; this.visibilityChange = "webkitvisibilitychange"; } this.listen(); }, listen: function() { if (!this.isSupport()) { window.addEventListener("focus", this.visibleHandler); window.addEventListener("blur", this.hiddenHandler); } else { var _this2 = this; document.addEventListener( this.visibilityChange, function() { if (!document[_this2.hidden]) { _this2.visibleHandler(); } else { _this2.hiddenHandler(); } }, 1 ); } } }; visibilityStore.init(); } startWatchBeatTimer() { var _this = this; if (!this.isWatchBeat || _this.isWatchBeatDurationDone || _this.watchBeatTimer) { return; } let _duration = _this.watchBeatDuration; if (!_this.isWatchBeatDurationDone) { _duration -= _this.watchBeatCount * 1e3; } _this.watchBeatTimer = setTimeout(function() { if (_this.isFunction(_this.watchBeatDurationDone)) { _this.watchBeatDurationDone(); _this.isWatchBeatDurationDone = true; _this.watchBeatCount = 0; } _this.stopWatchBeatTimer(); }, _duration); } stopWatchBeatTimer() { clearTimeout(this.watchBeatTimer); this.watchBeatTimer = null; } startBeat() { var _this = this; if (!_this.isSupportedLocalStorage()) { return; } if (this.beat_timer) { this.stopBeat(); } this.beat_timer = setInterval(function() { _this.console("====心跳"); _this.beat_count += 1; if (_this.isWatchBeat && _this.isFunction(_this.watchBeatUpdate) && !_this.isWatchBeatDurationDone) { _this.watchBeatCount += 1; _this.watchBeatUpdate(_this.watchBeatCount); } _this.saveBeatData(); }, this.beat_time); this.saveBeatData("first_beat"); } stopBeat() { clearInterval(this.beat_timer); this.beat_timer = null; this.beat_count = 0; } saveBeatData(type) { var _props = this.getProps(); var device_time = /* @__PURE__ */ new Date(); _props.$time = device_time; if (type === "first_beat") { _props.$event_duration = -1; } var data = this.extend({}, _props); var value = ""; try { value = JSON.stringify(data); } catch (err) { console.error(err); } window.localStorage.setItem(this.storage_name + "-" + this.page_id, encodeURIComponent(value)); if (data.$event_duration > this.min_duration) { this.automaticHandler(); this.console("====发送后重启心跳了"); } } removeBeatData(storage_key) { window.localStorage.removeItem( storage_key || this.storage_name + "-" + this.page_id ); } reSendBeatData() { var storage_length = window.localStorage.length; for (var i = storage_length - 1; i >= 0; i--) { var item_key = window.localStorage.key(i); if (item_key && item_key !== this.storage_name + "-" + this.page_id && item_key.indexOf(this.storage_name + "-") === 0) { var item_value = decodeURIComponent(window.localStorage.getItem(item_key) || ""); try { item_value = JSON.parse(item_value); } catch (error) { console.error(error); } if (this.isObject(item_value) && /* @__PURE__ */ new Date() * 1 - new Date(item_value.$time) * 1 > this.beat_time * 2) { this.sendData(item_value); this.removeBeatData(item_key); } } } } getProps() { var current_time = +/* @__PURE__ */ new Date(); var duration = current_time - this.start_time; var data = { $url: this.url, $current_time: current_time, $start_time: this.start_time }; if (duration !== 0) { data.$event_duration = duration; } data = this.extend({}, data); return data; } sendData(data) { if (data.$event_duration && data.$event_duration < this.min_limit || !data.$event_duration) { return; } this.console(this.isFunction(this.options.sendData)); this.isFunction(this.options.sendData) && this.options.sendData(data); } isString(arg) { return Object.prototype.toString.call(arg) == "[object String]"; } isNumber(arg) { return Object.prototype.toString.call(arg) == "[object Number]" && /[\d\.]+/.test(String(arg)); } isObject(arg) { if (arg == null) { return false; } else { return Object.prototype.toString.call(arg) == "[object Object]"; } } isFunction(arg) { if (!arg) { return false; } var type = Object.prototype.toString.call(arg); return type == "[object Function]" || type == "[object AsyncFunction]"; } isSupportedLocalStorage() { var supported = true; try { var supportName = "__support_localStorage__"; var val = "isSupportedLocalStorage"; window.localStorage.setItem(supportName, val); if (window.localStorage.getItem(supportName) !== val) { supported = false; }