大数据埋点sdk封装(二)
一、热启动两种情况
1、监听隐藏与显示
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
ctx.sendTracker({
event_type: 'hot_start',
});
} else if (document.visibilityState === 'hidden') {
ctx.sendTracker({
event_type: 'hot_quit',
});
}
});
2、菜单进入模块
data-jt-sender={JSON.stringify({type: 'clickAndSpm',event_type: 'hot_start'})}
二、TODO设置当前设备浏览器指纹,deviceId为空率低于0.5%,与官方宣称的99.5%符合,如果想更好,可考虑收费版或者其他库
const fpPromise = FingerprintJS.load();
const fp = await fpPromise;
const result = await fp.get();
ctx._mainObj.device_id = result.visitorId;
三、监听浏览器关闭、刷新、location.href跳转
window.addEventListener('unload', () => {
// TODO:添加关闭网页埋点
const sessionStore = {
...ctx.sessionStore,
_routerStack: ctx.router._routerStack,
_routerIndex: ctx.router._routerIndex,
};
sessionStorage.setItem('jimoTrackerSessionStore', JSON.stringify(sessionStore)); // 数据持久化
});
四、初始化从Storage获取参数
ctx.sessionStore = JSON.parse(sessionStorage.getItem('jimoTrackerSessionStore') || '{}');
ctx._mainObj.parameters.channel_type = ctx.sessionStore.channel_type;
ctx._mainObj.start_source = ctx.sessionStore.start_source || '';
const _routerStack = ctx.sessionStore._routerStack || [];
const _routerIndex = ctx.sessionStore._routerIndex || 0;
if (_routerStack[_routerIndex].key === history.state?.key) {
// 纯刷新,排除改网址情况
ctx.router._routerStack = _routerStack;
ctx.router._routerIndex = _routerIndex;
}
五、初始化从网址获取参数
const channel_type = getQueryString('channel_type') || undefined; // 后端消息类型
const inviter_id = getQueryString('_jt_inviterId') || undefined;
const share_type = getQueryString('_jt_shareType') || undefined;
const time = getQueryString('_jt_time') || undefined;
const pageDesc = getQueryString('_jt_pageDesc') || undefined;
const spm: any = getQueryString('_jt_spm') || undefined;
const spmArr = spm ? decodeURIComponent(spm).split('.') : [];
const obj = {
inviter_id,
share_type,
t: time,
from_page_desc: pageDesc && ctx._options.projectPath + decodeURIComponent(pageDesc),
from_block_desc: spmArr[2],
from_button_desc: spmArr[3],
};
ctx._shareObj = obj;
if (time) {
ctx.sessionStore.firstShareTime = time;
}
六、浏览器路由栈
1、网上资料
比如你顺序查看了 a,b,c 三个页面,我们就依次把 a,b,c 压入栈,这个时候,两个栈的数据就是这个样子:
点击后退,从页面 c 后退到页面 a 之后,我们就依次把 c 和 b 从栈 X 中弹出,并且依次放入到栈 Y。这个时候,两个栈的数据就是这个样子:
这时候想看 b,于是你又点击前进按钮回到 b 页面,我们就把 b 再从栈 Y 中出栈,放入栈 X 中。此时两个栈的数据是这个样子:
这个时候,你通过页面 b 又跳转到新的页面 d 了,页面 c 就无法再通过前进、后退按钮重复查看了,所以需要清空栈 Y。此时两个栈的数据这个样子:
2、针对我们的业务,用数组和指针简化
七、拦截路由pushState与replaceState添加SPM位置信息
/**
* 拦截pushState
*/
const interceptPushState = (router) => {
const oldPushState = history.pushState;
history.pushState = function (...args) {
oldPushState.apply(this, handleArgs(router, args));
router._routerStack.splice(++router._routerIndex);
router._routerStack.push({ key: args[0].key, path: args[2], pathname: router.tracker.getPathName() });
handleHistoryChange(router);
};
};
/**
* 拦截replaceState
*/
const interceptReplaceState = (router) => {
const oldReplaceState = history.replaceState;
history.replaceState = function (...args) {
oldReplaceState.apply(this, handleArgs(router, args));
router._routerStack[router._routerIndex] = {
key: args[0].key,
path: args[2],
pathname: router.tracker.getPathName(),
};
handleHistoryChange(router);
};
};
/**
* 跳转网址添加_jt_spm
*/
const handleArgs = (router, args) => {
const tracker = router.tracker;
if (tracker.curSpmObj) {
// try放里面,如果出错,外面可正常执行
const isFullUrl = args[2].startsWith('http'); // 是否完整地址
const urlObj = new URL(isFullUrl ? args[2] : location.origin + args[2]);
const searchParams = urlObj.searchParams;
searchParams.delete('_jt_spm');
searchParams.delete('_jt_pageDesc');
searchParams.append(
'_jt_spm',
`${tracker._options.spmPositionA}.${tracker.getPathName().slice(1)}.${tracker.curSpmObj.block_desc || '0'}.${tracker.curSpmObj.button_desc || '0'}`
);
args[2] = (isFullUrl ? location.origin : '') + urlObj.pathname + urlObj.search + urlObj.hash;
}
tracker.curSpmObj = null;
return args;
};
八、监听前进后退与back forward go
window.addEventListener('popstate', function (event) {
const index = router._routerStack.findIndex((item) => item.key === event.state?.key);
if (index > router._routerIndex) {
router.tracker.sendTracker({
event_type: 'hot_forward',
});
} else if (index < router._routerIndex) {
router.tracker.sendTracker({
event_type: 'hot_return',
});
}
router._routerIndex = index === -1 ? 0 : index;
handleHistoryChange(router);
});
九、路由变化时更新page_desc与page_refer
const handleHistoryChange = (router) => {
const tracker = router.tracker;
tracker._domSpmCache = new WeakMap(); // dom的spm位置信息缓存,dom移除或切换页面自动清除
const projectPath = tracker._options.projectPath;
const needNewRouter = tracker.needNewRouter;
const nowRouterStack = router._routerStack.slice(0, router._routerIndex + 1);
let index = nowRouterStack._jt_findLastIndex((item) => needNewRouter.includes(item.pathname));
index = index === -1 ? 0 : index;
const newRouterStack = nowRouterStack.slice(index);
// 拼接所有路径
tracker._mainObj.page_desc = newRouterStack?.reduce((a, b) => a + b.pathname, projectPath);
// 拼接来源路径;
if (nowRouterStack.length >= 2) {
const referIndex = nowRouterStack.length - 2;
tracker._mainObj.parameters.page_refer = nowRouterStack[referIndex].path;
} else {
tracker._mainObj.parameters.page_refer = undefined;
}
// 得到来源页面的spm值
const spm: any = getQueryString('_jt_spm') || undefined;
if (spm) {
const spmArr = decodeURIComponent(spm).split('.');
tracker._mainObj.parameters.pre_block_desc = spmArr[2] === '0' ? undefined : spmArr[2];
} else {
tracker._mainObj.parameters.pre_block_desc = undefined;
}
};
十、TODO:请求方法对比与封装js
1、神策由默认原来image方式改成navigator.sendBeacon,我们也采用此方式,添加兼容低版本
2、新建skd项目,用rollup打包全局iife格式js文件