yux-track-report
Version:
Buried point SDK for YUX
1,740 lines (1,739 loc) • 62.4 kB
JavaScript
"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;
}