如何優雅引入神策Web JS SDK

記得要微笑發表於2022-01-23

背景

公司以前採用GrowingIo埋點,都是在每個專案中引入其提供的Web JS SDK,但是最近由於使用期限到了,加上一些其他層面的考慮,準備採用神策做資料埋點和資料分析,因此需要移除每個專案中引入的GrowingIo Web JS SDK,然後引入神策提供的Web JS SDK

方案

如果還是採用在每個專案中獨立引入Web JS SDK,首先需要梳理出有多個專案有埋點需求,然後安排通知每個業務組的前端同學在迭代開發內引入。有沒有覺得特別麻煩,明明是同一個Web JS SDK卻需要多次引入,還涉及到跨組協作、任務分配、資源安排等問題。更重要的是,如果下次又更換採用另一個埋點工具,那重複的事情又要做一遍,吃力不討好。

因此,我們希望能夠在某個地方引入後,專案就不需要獨立引入了,這個方案有沒有想到跟閘道器服務、中介軟體、攔截器的思想特別像呢?我們是否能採用這類思想解決呢?答案是肯定的,通過在前端路由服務請求到的靜態html指令碼中注入神策Web JS SDK

路由服務引入神策.png

對於一些前後端分離專案(Vue、React)可以在前端路由服務中操作靜態html指令碼注入神策Web JS SDK,但對於一些前後端未分離的專案(例如freemarker專案)該怎麼處理呢?可以在webagent閘道器做統一處理嗎?其實也是可行的,攔截器可以攔截響應頭Content-Type: text/html的響應,操作html注入神策Web JS SDK;如果不是此響應型別,則不做處理。但在實際情況中,鑑於前後端未分離專案較少,我們還是採用第一種方案,獨立在專案中引入。

封裝神策Web JS SDK

神策有提供全埋點功能,專案中希望使用啟用該功能,因此需要再封裝,初始化SDK,可以參考如下封裝:

;(function () {
  /** 判斷環境 */
  var hostname = window.location.hostname;
  var serverUrl = 'https://xxx?project=default'; // 測試環境資料接收地址
  if (hostname === 'www.cassmall.com' || hostname === 'h.cassmall.com') {
    serverUrl = 'https://xxx?project=production'; // 生產環境資料接收地址
  }
  // 開啟全埋點,Web JS SDK 全埋點包括三種事件:Web 頁面瀏覽、Web 元素點選、Web 視區停留
  function addShenCeScript() {
    window.cassSensors = window['sensorsDataAnalytic201505'];
        // 初始化 SDK
    window.cassSensors.init({
      server_url: serverUrl, // 資料接收地址
      is_track_single_page: true, // 單頁面配置,預設開啟,若頁面中有錨點設計,需要將該配置刪除,否則觸發錨點會多觸發 $pageview 事件
      heatmap: {
                /**
                * Web 元素點選($WebClick)
                * 是否開啟點選圖,default 表示開啟,自動採集 $WebClick 事件,可以設定 'not_collect' 表示關閉。
                * 預設只有點選 a input button textarea 四種元素時,才會觸發 $WebClick 元素點選事件
                */
        clickmap: 'default',
                /**
                * 視區停留事件($WebStay)
                * 是否開啟觸達圖,default 表示開啟,自動採集 $WebStay 事件,可以設定 'not_collect' 表示關閉。
                * 需要 Web JS SDK 版本號大於 1.9.1
                */
        scroll_notice_map: 'default',
                // 通過 collect_tags 配置是否開啟其他任意元素的全埋點採集(預設不採集),其中 div 通過配置最多可以採集 3 層巢狀情況。
        collect_tags: { 
          div: {
            max_level: 3, // 預設是 1,即只支援葉子 div。可配置範圍是 [1, 2, 3],非該範圍配置值,會被當作 1 處理。
          },
          li: true,
          span: true,
          i: true,
          img: true
        },
      },
    })
        // 註冊公共屬性
    window.cassSensors.registerPage({
      platform_type: 'web',
      path_name: window.location.pathname,
    })
        /**
        * Web 頁面瀏覽($pageview)
        * 設定之後,SDK 就會自動收集頁面瀏覽事件,以及設定初始來源。
        */
    window.cassSensors.quick('autoTrack');

        /** 獲取使用者登入ID,使用者登入後,開發人員呼叫login,將使用者登入ID傳給SDK,後續該裝置上所有事件的distinct_id就會變成使用者所對應的登入ID。*/
    ajax({
      url: '/webim/user/jwt_token',
      type: 'GET',
      success: function (res) {
        try {
          var data = JSON.parse(res);
          window.cassSensors.login(data.username);
        } catch (e) { }
      },
      error: function (error) { }
    });
  }

  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');

  var explorer = window.navigator.userAgent;
  if (explorer.indexOf('MSIE') >= 0) {
    // ie
    script.onreadystatechange = function () {
      if (this.readyState === 'loaded' || this.readyState === 'complete') {
        addShenCeScript();
      }
    }
  } else {
    // chrome
    script.onload = function () {
      addShenCeScript();
    }
  }
  script.setAttribute(
    'src',
    'https://mstatic.cassmall.com/assets/sensors/sensorsdata1.19.4.min.js'
  );
    
    /** 封裝ajax請求 */
  function ajax(params) {
    params = params || {};
    params.data = params.data || {};
    // 判斷是ajax請求還是jsonp請求
    var json = params.jsonp ? jsonp(params) : json(params);
    // ajax請求 
    function json(params) {
      // 請求方式,預設是GET
      params.type = (params.type || 'GET').toUpperCase();
      // 避免有特殊字元,必須格式化傳輸資料
      params.data = formatParams(params.data);
      var xhr = null;

      // 例項化XMLHttpRequest物件 
      if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
      } else {
        // IE6及其以下版本 
        xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
      };

      // 監聽事件,只要 readyState 的值變化,就會呼叫 readystatechange 事件
      xhr.onreadystatechange = function () {
        // readyState屬性表示請求/響應過程的當前活動階段,4為完成,已經接收到全部響應資料
        if (xhr.readyState == 4) {
          var status = xhr.status;
          // status:響應的HTTP狀態碼,以2開頭的都是成功
          if (status >= 200 && status < 300) {
            var response = '';
            // 判斷接受資料的內容型別
            var type = xhr.getResponseHeader('Content-type');
            if (type.indexOf('xml') !== -1 && xhr.responseXML) {
              response = xhr.responseXML; //Document物件響應 
            } else if (type === 'application/json') {
              response = JSON.parse(xhr.responseText); //JSON響應 
            } else {
              response = xhr.responseText; //字串響應 
            };
            // 成功回撥函式
            params.success && params.success(response);
          } else {
            params.error && params.error(status);
          }
        };
      };

      // 連線和傳輸資料 
      if (params.type == 'GET') {
        // 三個引數:請求方式、請求地址(get方式時,傳輸資料是加在地址後的)、是否非同步請求(同步請求的情況極少);
        xhr.open(params.type, params.url + '?' + params.data, true);
        xhr.send(null);
      } else {
        xhr.open(params.type, params.url, true);
        //必須,設定提交時的內容型別 
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
        // 傳輸資料
        xhr.send(params.data);
      }
    }

    //格式化引數 
    function formatParams(data) {
      var arr = [];
      for (var name in data) {
        // encodeURIComponent() :用於對 URI 中的某一部分進行編碼
        arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
      };
      // 新增一個隨機數引數,防止CDN快取 
      arr.push('v=' + random());
      return arr.join('&');
    }

    // 獲取隨機數 
    function random() {
      return Math.floor(Math.random() * 10000 + 500);
    }
  }
  if (!window.cassSensors) {
    document.getElementsByTagName('head')[0].appendChild(script);
  }
})()

相關文章