zoukankan      html  css  js  c++  java
  • 无侵入埋点

    export default function (options) {
      var defaultOptions = {
        responseValidate: function (response = {}, ctx) {
          return response.code === 0
        },
        reportUrl: '/wofBuriedPoint/report',
        expireTime: 2 * 60 * 60 * 1000, // 会话过期时间
        types: {
          url: 'url', // 地址上报
          click: 'click', // click事件上报
          request: 'request', // 请求后端接口上报
          error: {
            script: 'script-error', // 代码运行错误上报
            request: 'request-error' // 请求错误上报
          }
        },
        sessionKey: '_detect', // 会话存储键值
        env: process.env.NODE_ENV
      }
      var { responseValidate, reportUrl, expireTime, types, sessionKey, env } = { ...defaultOptions, ...options }
      if (env === 'production') {
        XmlProxy()
        ErrorProxy()
        window.addEventListener('error', e => {
          errTrigger(e.error)
        })
        history.pushState = _wr('pushState', history.pushState)
        history.replaceState = _wr('replaceState', history.replaceState)
        window.addEventListener('replaceState', urlListener)
        window.addEventListener('pushState', urlListener)
        window.addEventListener('popstate', urlListener)
        window.addEventListener('hashchange', urlListener)
        window.addEventListener('load', load)
        window.addEventListener('unload', leave)
        window.addEventListener('focus', function () {
          visible()
        })
        window.addEventListener('blur', function () {
          hide()
        })
        window.addEventListener('click', capturingClick, true)
      }
      function sessionGen () {
        var location = parseLocation()
        var performance = xhperf()
        var agent = parseAgent()
        let { host, hash, href, path } = location
        let { domainLookupTime, connectTime, requestTime, responseTime, domParsingTime, domContentLoadedTime } = performance
        return {
          sid: getUUID(),
          startTime: nowTime(), // 会话开始时间
          step: 0,
          during: 0,
          visibleStartTime: nowTime(), // 页面显示开始时间
          agent,
          referrer: document.referrer,
          domainLookupTime,
          connectTime,
          requestTime,
          responseTime,
          domParsingTime,
          domContentLoadedTime,
          locationHost: host,
          locationHash: hash,
          locationHref: href,
          locationPath: path
        }
      }
      // 内部跳转
      function urlListener () {
        // report from
        leave()
        // set to
        enter()
      }
      // 页面加载
      function load () {
        var session = sessionGen()
        setSession(session)
      }
      // 页面离开
      function leave () {
        var session = getSession()
        if (session) {
          session.during += nowTime() - session.visibleStartTime
          var detect = _detect(session, types.url)
          report(detect)
        }
      }
      // 页面内部跳转进入
      function enter () {
        var session = getSession()
        if (session) {
          session.step += 1
          session.during = 0
          session.visibleStartTime = nowTime()
          let { host, href, hash, path } = parseLocation()
          session.locationHost = host
          session.locationHref = href
          session.locationPath = path
          session.locationHash = hash
          setSession(session)
        }
      }
      // 页面显示
      function visible () {
        var session = getSession()
        if (session) {
          // 如果页面隐藏时间过长,视为从新建立会话
          var leaveTime = nowTime() - session.visibleStartTime
          if (leaveTime > expireTime) {
            // 旧数据上报
            var detect = _detect(session, types.url)
            report(detect)
            // 重置会话
            session = sessionGen()
          } else {
            session.visibleStartTime = nowTime()
          }
          setSession(session)
        }
      }
      // 页面隐藏
      function hide () {
        var session = getSession()
        if (session) {
          session.during += nowTime() - session.visibleStartTime
          session.visibleStartTime = nowTime() // 重置显示开始时间以便再次显示时计算页面隐藏时间
          setSession(session)
        }
      }
      // 捕获点击事件
      function capturingClick (e) {
        var target = e.target
        var btnName = ''
        var result = isButton(target)
        if (result) {
          if (result.tagName === 'INPUT') {
            btnName = result.value
          } else {
            btnName = result.outerText
          }
          var session = getSession()
          if (session) {
            const detect = _detect(session, types.click, btnName)
            report(detect)
          }
        }
      }
      function report (detect) {
        if (detect && detect.sid) {
          window.requestIdleCallback
            ? window.requestIdleCallback(
              function () {
                request(detect)
              },
              { timeout: 2000 }
            )
            : request(detect)
        }
      }
      function _detect (session, type = types.url, content = '') {
        let detect = {
          ...session,
          content,
          type,
          time: nowTime()
        }
        // 格式化时间
        detect.startTime = dateFormat('yyyy-MM-dd hh:mm:ss.S', new Date(detect.startTime))
        detect.time = dateFormat('yyyy-MM-dd hh:mm:ss.S', new Date(detect.time))
        return detect
      }
      function nowTime () {
        return new Date().getTime()
      }
      function getSession () {
        var session = sessionStorage.getItem(sessionKey)
        return session ? JSON.parse(session) : session
      }
      function setSession (obj) {
        var session = getSession()
        session = { ...session, ...obj }
        sessionStorage.setItem(sessionKey, JSON.stringify(session))
      }
      // 浏览器信息
      function parseAgent () {
        return window.navigator.userAgent
      }
      // 页面性能监控
      function xhperf () {
        if (window.performance) {
          var timing = window.performance.timing
          var domainLookupTime = timing.domainLookupEnd - timing.domainLookupStart // DNS 域名解析时长
          var connectTime = timing.connectEnd - timing.connectStart // TCP 链接建立时长
          var requestTime = timing.responseStart - (timing.requestStart || timing.responseStart + 1) // 页面请求时长
          var responseTime = timing.responseEnd - timing.responseStart // 资源响应时长
          timing.domContentLoadedEventStart ? responseTime < 0 && (responseTime = 0) : (responseTime = -1)
          var domParsingTime = timing.domContentLoadedEventStart ? timing.domInteractive - timing.domLoading : -1 // DOM解析时长
          var domContentLoadedTime = timing.domContentLoadedEventStart
            ? timing.domContentLoadedEventStart - timing.fetchStart
            : -1 // 文档全解析时长
          return {
            domainLookupTime,
            connectTime,
            requestTime,
            responseTime,
            domParsingTime,
            domContentLoadedTime
          }
        } else {
          return ''
        }
      }
      function parseLocation () {
        var location = window.location
        var host = location.hostname
        var hash = location.hash
        if (hash.includes('#')) {
          hash = hash.toString().slice(1)
        }
        var search = location.search
        if (search.includes('?')) {
          var params = search
            .toString()
            .slice(1)
            .split('&')
            .reduce((pre, curr) => {
              var arr = curr.split('=')
              pre[arr[0]] = arr[1]
              return pre
            }, {})
        }
        var href = location.href
        var path = location.pathname
        return {
          host,
          hash,
          params,
          href,
          path
        }
      }
      // 判断是否是A和BUTTON或其子元素
      function isButton (target) {
        if (target === null) {
          return false
        } else {
          if (
            target.tagName === 'A' ||
            target.tagName === 'BUTTON' ||
            (target.tagName === 'INPUT' && target.type === 'button')
          ) {
            return target
          } else {
            return isButton(target.parentElement)
          }
        }
      }
    
      function XmlProxy () {
        var _open = XMLHttpRequest.prototype.open
        if (_open) {
          XMLHttpRequest.prototype.open = new Proxy(_open, {
            apply: function (target, ctx, args) {
              var _requestURL = args[1]
              ctx._isReportUrl = _requestURL === reportUrl
              // 上报接口不要拦截
              if (!ctx._isReportUrl) {
                ctx._session = getSession() // xhr打开时缓存session
                ctx._method = args[0]
                ctx._requestURL = args[1]
              }
              return Reflect.apply(...arguments)
            }
          })
        }
        var _send = XMLHttpRequest.prototype.send
        if (_send) {
          XMLHttpRequest.prototype.send = new Proxy(_send, {
            apply: function (target, ctx, args) {
              // 上报接口不要拦截
              if (!ctx._isReportUrl) {
                ctx._requestText = args[0]
                ctx.onreadystatechange = onreadystatechangeProxy(ctx.onreadystatechange)
                ctx.onerror = onerrorProxy(ctx.onerror)
              }
              return Reflect.apply(...arguments)
            }
          })
        }
      }
      function onreadystatechangeProxy (_onreadystatechange) {
        if (_onreadystatechange) {
          return new Proxy(_onreadystatechange, {
            apply: function (target, ctx, args) {
              if (ctx.readyState === 4) {
                var detect = null
                var session = ctx._session
                var content = null
                if (ctx.status >= 200 && ctx.status < 300) {
                  if (responseValidate instanceof Function) {
                    var response = ctx.responseText ? JSON.parse(ctx.responseText) : {}
                    if (responseValidate(response, ctx)) {
                      content = requestFormat(ctx, true)
                      detect = _detect(session, types.request, content)
                    } else {
                      content = requestFormat(ctx)
                      detect = _detect(session, types.error.request, content)
                    }
                  }
                } else if (ctx.status >= 400) {
                  content = requestFormat(ctx)
                  detect = _detect(session, types.error.request, content)
                }
                content && detect && report(detect)
              }
              return Reflect.apply(...arguments)
            }
          })
        } else {
          return _onreadystatechange
        }
      }
      function onerrorProxy (_onerror) {
        if (_onerror) {
          return new Proxy(_onerror, {
            apply: function (target, ctx, args) {
              var session = ctx._session
              var content = requestFormat(ctx)
              var detect = _detect(session, types.error.request, content)
              content && detect && report(detect)
              return Reflect.apply(...arguments)
            }
          })
        } else {
          return _onerror
        }
      }
      function ErrorProxy () {
        console.error = new Proxy(console.error, {
          apply: function (target, ctx, args) {
            errTrigger(new Error(args))
            Reflect.apply(...arguments)
          }
        })
      }
      function errTrigger (error = {}) {
        if (error) {
          var content = JSON.stringify({
            message: error.message,
            stack: error.stack
          })
          var session = getSession()
          if (session) {
            var detect = _detect(session, types.error.script, content)
            report(detect)
          }
        }
      }
      function requestFormat (xhr, success = false) {
        const result = {
          status: xhr.status,
          method: xhr._method,
          path: xhr._requestURL,
          requestText: (xhr._requestText || '').toString().slice(0, 500),
          responseText: (success ? '' : xhr.responseText || '').toString().slice(0, 500) // 请求成功时,不必上报请求结果
        }
        return JSON.stringify(result)
      }
      // 添加监控事件
      function _wr (type, orig) {
        return new Proxy(orig, {
          apply: function (target, ctx, args) {
            var e = new Event(type)
            e.arguments = arguments
            window.dispatchEvent(e)
            Reflect.apply(...arguments)
          }
        })
      }
      // 生成一个不重复的uuid
      function getUUID () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
          let r = (Math.random() * 16) | 0
          let v = c === 'x' ? r : (r & 0x3) | 0x8
          return v.toString(16)
        })
      }
      function request (params) {
        if (window.navigator.sendBeacon) {
          window.navigator.sendBeacon(reportUrl, JSON.stringify(params))
        } else {
          let xhr = new XMLHttpRequest()
          xhr.open('post', reportUrl)
          xhr.setRequestHeader('Content-Type', 'application/json')
          xhr.send(JSON.stringify(params))
        }
      }
      function dateFormat (fmt, date) {
        var o = {
          'M+': date.getMonth() + 1, // 月份
          'd+': date.getDate(), //
          'h+': date.getHours(), // 小时
          'm+': date.getMinutes(), //
          's+': date.getSeconds(), //
          'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
          'S': date.getMilliseconds() // 毫秒
        }
        if (/(y+)/.test(fmt)) {
          fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
        }
        for (var k in o) {
          if (new RegExp('(' + k + ')').test(fmt)) {
            fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
          }
        }
        return fmt
      }
    }
  • 相关阅读:
    POJ 3694 Network (求桥,边双连通分支缩点,lca)
    UVA 796
    UVA 315 315
    POJ 1236 Network of Schools (有向图的强连通分量)
    【转】有向图强连通分量的Tarjan算法
    HDU 3072 Intelligence System (强连通分量)
    【转】强大的vim配置文件,让编程更随意
    WORDPRESS登录后台半天都无法访问或者是访问慢的解决方法
    phpstorm+Xdebug断点调试PHP
    PHP性能调优---PHP调试工具Xdebug安装配置教程
  • 原文地址:https://www.cnblogs.com/zhoulixue/p/12605340.html
Copyright © 2011-2022 走看看