大数据埋点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 压入栈,这个时候,两个栈的数据就是这个样子:
image
点击后退,从页面 c 后退到页面 a 之后,我们就依次把 c 和 b 从栈 X 中弹出,并且依次放入到栈 Y。这个时候,两个栈的数据就是这个样子:
image
这时候想看 b,于是你又点击前进按钮回到 b 页面,我们就把 b 再从栈 Y 中出栈,放入栈 X 中。此时两个栈的数据是这个样子:
image
这个时候,你通过页面 b 又跳转到新的页面 d 了,页面 c 就无法再通过前进、后退按钮重复查看了,所以需要清空栈 Y。此时两个栈的数据这个样子:
image

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文件