大数据埋点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'})}

二、监听浏览器关闭、刷新、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;
    }
};

九、请求方法封装

神策由默认原来image方式改成navigator.sendBeacon,我们也采用此方式,添加兼容低版本

function imgRequest(url, data) {
  const image = new Image();
  image.onload = function () {
    image = null;
  };
  image.src = `${url}?${stringify(data)}`;
}

function sendBeacon(url, data) {
  let headers = {
    type: "application/json",
  };
  let blob = new Blob([JSON.stringify(data)], headers);
  navigator.sendBeacon(url, blob);
}

function xmlHttpRequest(url, data) {
  const client = new XMLHttpRequest();
  client.open("POST", url, false);
  client.setRequestHeader("Content-Type", "application/json; charset=utf-8");
  client.send(JSON.stringify(data));
}

十、rollup打包全局iife格式js文件

新建skd项目,用rollup打包全局iife格式js文件

export default {
  input: "src/index.js", //输入文件
  output: {
    file: "dist/jimoTracker.js", //输出文件
    format: "iife", //打包格式,全局自执行函数
    name: "_jt_", //window挂载_jt_
  },
};
posted @ 2022-01-05 10:58  jerry-mengjie  阅读(277)  评论(0)    收藏  举报