Bot Challenge中的behavior collection分析

很久沒有寫博客了舌剂,主要是最近換了個(gè)地打工,開始對(duì)一些反自動(dòng)化的工作開始進(jìn)行研究焕数;這是一篇學(xué)習(xí)筆記辆它,歡迎交流~

背景與網(wǎng)站介紹

Bot Challenge是專門的web bot檢測的網(wǎng)站:https://bot.incolumitas.com/#botChallenge

該網(wǎng)站對(duì)用戶行為的檢測手段很完整,值得學(xué)習(xí)获茬;

image

用戶行為數(shù)據(jù)

總體收集的event

this.recordedEvents = ["mousemove", "mousedown", "mouseup", "dblclick", "contextmenu", "scroll", "resize", "keydown", "keyup", "touchstart", "touchmove", "touchcancel", "touchend", "load", "DOMContentLoaded", "visibilitychange", "pagehide", "beforeunload", "unload"],

this.newRecordedEvents = ["copy", "paste", "deviceorientation", "devicemotion"]

this.onlyWindowEvent = ["scroll", "keydown", "keyup", "resize", "copy", "paste", "deviceorientation", "devicemotion", "visibilitychange", "load", "DOMContentLoaded", "pagehide", "beforeunload", "unload"],

this.recordNewEvents && (this.recordedEvents = this.recordedEvents.concat(this.newRecordedEvents))

具體行為收集可以分為以下幾類港庄,主要分析下收集的具體數(shù)據(jù)和觸發(fā)收集的事件

鼠標(biāo)動(dòng)作(MouseEvent)

getMetaKeysBitstring: function(e) {
    var t = "";
    return t += !0 === e.ctrlKey ? "1" : "0",
    t += !0 === e.shiftKey ? "1" : "0",
    t += !0 === e.altKey ? "1" : "0",
    t += !0 === e.metaKey ? "1" : "0"
}
getMouseFrame: function(e, t) {
    return [t, e.clientX, e.clientY, e.screenX, e.screenY, e.button, this.getMetaKeysBitstring(e)]
},
mousemoveListener: function(e, t) {
    return e.getMouseFrame(t, "m")
},
mousedownListener: function(e, t) {
    return e.getMouseFrame(t, "md")
},
mouseupListener: function(e, t) {
    return e.getMouseFrame(t, "mu")
},
dblclickListener: function(e, t) {
    return e.getMouseFrame(t, "dc")
},
contextmenuListener: function(e, t) {
    return e.getMouseFrame(t, "cm")
},

數(shù)據(jù)(列表形式,都是以事件名簡寫打頭恕曲,后面是收集的具體數(shù)據(jù)鹏氧,下同):

  • clientX:double(原為long);鼠標(biāo)在事件觸發(fā)時(shí)的應(yīng)用瀏覽器內(nèi)的水平坐標(biāo)

  • clientY:double(原為long)佩谣;鼠標(biāo)在事件觸發(fā)時(shí)的應(yīng)用瀏覽器內(nèi)的垂直坐標(biāo)

  • screenX:double(原為long)把还;鼠標(biāo)在事件觸發(fā)時(shí)全局(屏幕)中的水平坐標(biāo)

  • screenY:double(原為long);鼠標(biāo)在事件觸發(fā)時(shí)全局(屏幕)中的垂直坐標(biāo)

  • button: number茸俭;代表事件觸發(fā)時(shí)按下的鼠標(biāo)按鍵:

    • 0:主按鍵吊履,通常指鼠標(biāo)左鍵或默認(rèn)值(譯者注:如document.getElementById('a').click()這樣觸發(fā)就會(huì)是默認(rèn)值)
  • 1:輔助按鍵,通常指鼠標(biāo)滾輪中鍵

  • 2:次按鍵调鬓,通常指鼠標(biāo)右鍵

  • 3:第四個(gè)按鈕艇炎,通常指瀏覽器后退按鈕

  • 4:第五個(gè)按鈕,通常指瀏覽器的前進(jìn)按鈕

  • MetaKey:String腾窝;收集觸發(fā)事件時(shí)對(duì)應(yīng)按鍵是否被按下缀踪;'0'與'1'組成的字符串

事件:

  • mousemove:鼠標(biāo)移動(dòng)
  • mousedown:鼠標(biāo)按鈕按下時(shí)觸發(fā)
  • mouseup:鼠標(biāo)按鈕松開時(shí)觸發(fā)
  • dblclick:鼠標(biāo)雙擊時(shí)觸發(fā)
  • contextmenu:打開上下文菜單時(shí)觸發(fā)居砖,例如在頁面右鍵打開菜單

鍵盤動(dòng)作(KeyboardEvent)

getKeyFrame: function(e, t) {
    return [t, e.code, e.key, e.location, e.repeat, this.getMetaKeysBitstring(e)]
},
keydownListener: function(e, t) {
    return e.getKeyFrame(t, "kd")
},
keyupListener: function(e, t) {
    return e.getKeyFrame(t, "ku")
},

數(shù)據(jù):

  • code:String;鍵盤上的物理鍵(與按鍵生成的字符相對(duì))驴娃。換句話說奏候,此屬性返回一個(gè)值,該值不會(huì)被鍵盤布局或修飾鍵的狀態(tài)改變托慨。如QWERTY布局鍵盤上的“q”鍵返回的code是“KeyQ
  • key: String鼻由;返回用戶按下的真實(shí)邏輯輸入暇榴。它還與 shiftKey 等調(diào)節(jié)性按鍵的狀態(tài)和鍵盤的區(qū)域 / 和布局有關(guān)厚棵。
  • location: unsigned long,表示按鍵在鍵盤或其他設(shè)備上的位置, 主要針對(duì)ctrl/shift等鍵盤上有多個(gè)的按鍵蔼紧,以及數(shù)字/enter等按鍵:
    • 0: 表示不區(qū)分或者無法區(qū)分
    • 1: 來自左邊的ctrl/shift/alt...
    • 2: 來自右邊的按鍵
    • 3: 來自數(shù)字小鍵盤的按鍵
    • 其他值已廢棄
  • repeat: Bool婆硬;如果按鍵被一直按住,返回值為true
  • Metakey: 與鼠標(biāo)事件一樣

事件:

  • keydown:鍵盤按下觸發(fā)
  • keyup:鍵盤松開觸發(fā)

觸摸動(dòng)作(TouchEvent)

getTouchFrame: function(e, t) {
    for (var n = [], i = 0; i < e.touches.length; i++) {
        var a = e.touches[i]
          , o = [this.round2(a.clientX), this.round2(a.clientY), this.round2(a.screenX), this.round2(a.screenY), a.identifier];
        this.mobileExperimental && (o = o.concat([this.round2(a.radiusX), this.round2(a.radiusY), a.rotationAngle, a.force])),
        n.push(o)
    }
    return [t, n, this.getMetaKeysBitstring(e)]
},
touchstartListener: function(e, t) {
    return e.getTouchFrame(t, "ts")
},
touchmoveListener: function(e, t) {
    return e.getTouchFrame(t, "tm")
},
touchcancelListener: function(e, t) {
    return e.getTouchFrame(t, "tc")
},
touchendListener: function(e, t) {
    return e.getTouchFrame(t, "te")
},

數(shù)據(jù):

  • touches: List奸例;是一個(gè)touchList彬犯,一個(gè)觸摸平面上所有觸點(diǎn)的列表。例如查吊,如果一個(gè)用戶用三根手指接觸屏幕(或者觸控板)谐区,與之對(duì)應(yīng)的 TouchList 會(huì)包含每根手指的 [Touch](https://developer.mozilla.org/zh-CN/docs/Web/API/Touch) 對(duì)象,總共三個(gè)
    • touch.clientX/Touch.clientY/Touch.screenX/Touch.screenY:double (之前為long)逻卖;同鼠標(biāo)事件同名屬性
    • touch.identifier:long宋列;返回一個(gè)可以唯一地識(shí)別和觸摸平面接觸的點(diǎn)的值. 這個(gè)值在這根手指(或觸摸筆等)所引發(fā)的所有事件中保持一致, 直到它離開觸摸平面;主要是touchmove中

底下的事件將是Experimental功能:

  • touch.radiusX:float评也;手指與屏幕接觸面的橢圓水平軸半徑
  • touch.radiusY:float炼杖;手指與屏幕接觸面的橢圓垂直軸半徑
  • touch.rotationAngle: float;返回以度為單位的旋轉(zhuǎn)角. 由radiusXradiusY 描述的正方向的橢圓盗迟,通過順時(shí)針旋轉(zhuǎn)這個(gè)角度后坤邪,能最精確地覆蓋住用戶和觸摸平面的接觸面的角度. 這個(gè)值可能從0到90
  • touch.force:float;手指擠壓觸摸平面的壓力大小, 從0.0(沒有壓力)到1.0(最大壓力)

事件:

  • touchstart: 當(dāng)用戶在觸摸平面上放置了一個(gè)觸點(diǎn)時(shí)觸發(fā)

  • touchmove: 當(dāng)用戶在觸摸平面上移動(dòng)觸點(diǎn)時(shí)觸發(fā); 當(dāng)觸點(diǎn)的半徑罚缕、旋轉(zhuǎn)角度以及壓力大小發(fā)生變化時(shí)艇纺,也將觸發(fā)此事件

  • touchcancel: 當(dāng)觸點(diǎn)由于某些原因被中斷時(shí)觸發(fā)。有幾種可能的原因如下(具體的原因根據(jù)不同的設(shè)備和瀏覽器有所不同):

    • 由于某個(gè)事件出現(xiàn)而取消了觸摸:例如觸摸過程被彈窗打斷邮弹。
  • 觸點(diǎn)離開了文檔窗口喂饥,而進(jìn)入了瀏覽器的界面元素、插件或者其他外部內(nèi)容區(qū)域肠鲫。

  • 當(dāng)用戶產(chǎn)生的觸點(diǎn)個(gè)數(shù)超過了設(shè)備支持的個(gè)數(shù)员帮,從而導(dǎo)致 [TouchList](https://developer.mozilla.org/zh-CN/docs/Web/API/TouchList) 中最早的 [Touch] 對(duì)象被取消。

  • touchend: 當(dāng)一個(gè)觸點(diǎn)被用戶從觸摸平面上移除(即用戶的一個(gè)手指或手寫筆離開觸摸平面)時(shí)觸發(fā)导饲。當(dāng)觸點(diǎn)移出觸摸平面的邊界時(shí)也將觸發(fā)捞高。例如用戶將手指劃出屏幕邊緣

元素移動(dòng)相關(guān)

scrollListener: function(e, t) {
    return ["s", e.round2(document.scrollingElement.scrollLeft), e.round2(document.scrollingElement.scrollTop)]
}
resizeListener: function(e, t) {
    return ["r", window.innerWidth, window.innerHeight]
},

  • ScrollEvent:文檔視圖或者一個(gè)元素在滾動(dòng)時(shí)氯材,會(huì)觸發(fā); 主要是收集滾動(dòng)條數(shù)據(jù)
    • scrollingElement.scrollLeft:integer(有比例縮放的系統(tǒng)可能為float);滾動(dòng)條到最左邊的距離
    • scrollingElement.scrollTop:integer(有比例縮放的系統(tǒng)可能為float)硝岗;滾動(dòng)條到最頂端的距離
  • resizeEvent:調(diào)整視窗大小時(shí)觸發(fā)該事件
    • window.innerWidth:integer氢哮;返回以像素為單位的窗口的內(nèi)部寬度。如果垂直滾動(dòng)條存在型檀,則這個(gè)屬性將包括它的寬度冗尤。
    • window.innerHeight:integer;返回以像素為單位的窗口的內(nèi)部高度度胀溺。如果有水平滾動(dòng)條裂七,也包括滾動(dòng)條高度。

頁面相關(guān)事件

主要是頁面加載仓坞,tab切換等:

loadListener: function(e, t) {
    return ["lo"]
},
DOMContentLoadedListener: function(e, t) {
    return ["dcl"]
},
visibilitychangeListener: function(e, t) {
    return ["vc", document.visibilityState]
},
pagehideListener: function(e, t) {
    return ["ph", t.persisted]
},
beforeunloadListener: function(e, t) {
    return ["bu"]
},
unloadListener: function(e, t) {
    return ["ul"]
},

  • load:當(dāng)整個(gè)頁面及所有依賴資源如樣式表和圖片都已完成加載時(shí)背零,將觸發(fā)

  • DOMContentLoaded:當(dāng)純HTML被完全加載以及解析時(shí),事件會(huì)被觸發(fā)无埃,而不必等待樣式表徙瓶,圖片或者子框架完成加載

  • visibilitychange:當(dāng)其選項(xiàng)卡的內(nèi)容變得可見或被隱藏時(shí),會(huì)在文檔上觸發(fā)

    • document.visibilityState:String嫉称;返回document的可見性, 即當(dāng)前可見元素的上下文環(huán)境. 由此可以知道當(dāng)前文檔(即為頁面)是在背后, 或是不可見的隱藏的標(biāo)簽頁侦镇,或者(正在)預(yù)渲染.可用的值如下:
      • 'visible' : 此時(shí)頁面內(nèi)容至少是部分可見. 即此頁面在前景標(biāo)簽頁中,并且窗口沒有最小化.
  • 'hidden' : 此時(shí)頁面對(duì)用戶不可見. 即文檔處于背景標(biāo)簽頁或者窗口處于最小化狀態(tài)织阅,或者操作系統(tǒng)正處于 '鎖屏狀態(tài)' .

  • 'prerender' : 頁面此時(shí)正在渲染中, 因此是不可見的 (considered hidden for purposes of document.hidden). 文檔只能從此狀態(tài)開始壳繁,永遠(yuǎn)不能從其他值變?yōu)榇藸顟B(tài).注意: 瀏覽器支持是可選的.

  • pagehide:當(dāng)瀏覽器在隱藏當(dāng)前頁面時(shí), 頁面隱藏事件會(huì)被發(fā)送到一個(gè)window 。例如蒲稳,當(dāng)用戶單擊瀏覽器的“后退”按鈕時(shí)氮趋,當(dāng)前頁面在顯示上一頁之前會(huì)收到一個(gè)頁面隱藏事件。

    • persisted:代表一個(gè)頁面是否從緩存中加載的江耀,可以判斷隱藏頁面是否已緩存以進(jìn)行可能的重用時(shí)執(zhí)行特殊處理
  • beforeunload:window剩胁、document 和它們的資源即將卸載時(shí)觸發(fā),例如可以彈窗確定是否關(guān)閉選項(xiàng)卡

  • unload:window祥国、document 和它們的資源正在卸載時(shí)觸發(fā)

用戶操作相關(guān)

  • Copy & paste
copyListener: function(e, t) {
    var n = document.getSelection()
      , i = ["co"];
    return n && i.push(Math.abs(n.anchorOffset, n.focusOffset)),
    i
},

pasteListener: function(e, t) {
    return ["pa", (t.clipboardData || window.clipboardData).getData("text").length]
},

  • getSelection:返回一個(gè)選中對(duì)象
    • selection.anchorOffset: integer昵观;返回選中元素在DOM節(jié)點(diǎn)中起始位置(按下鼠標(biāo))偏移
    • selection.focusOffset:integer;返回選中元素在DOM節(jié)點(diǎn)中終止位置(松開鼠標(biāo))偏移

例子:

<text>abcdefg<text>

若選中該text元素內(nèi)的"bcd"舌稀,則anchorOffset = 1啊犬,focusOffset = 3

  • clipboardData. getData("text").length: integer;粘貼板上字符串長度

  • Deviceorientation: 設(shè)備(指手機(jī)壁查,平板等移動(dòng)設(shè)備)在瀏覽頁面時(shí)物理旋轉(zhuǎn)的信息觉至;注意safari未實(shí)現(xiàn)

deviceorientationListener: function(e, t) {
    if (!(Math.abs(e.rotateDegrees - t.alpha) < 2 || Math.abs(e.leftToRight - t.gamma) < 1 || Math.abs(e.frontToBack - t.beta) < 1)) {
        e.rotateDegrees = t.alpha,
        e.frontToBack = t.beta,
        e.leftToRight = t.gamma;
        t = t.absolute;
        return null !== e.rotateDegrees && null !== e.frontToBack && null !== e.leftToRight ? ["do", e.round2(e.rotateDegrees), e.round2(e.frontToBack), e.round2(e.leftToRight), t] : void 0
    }
},

收集邏輯以1度為精度,若誤差小于一度則不記錄

  • alpha:double睡腿;一個(gè)表示設(shè)備繞z軸旋轉(zhuǎn)的角度(范圍在0-360之間)的數(shù)字

  • beta:double:一個(gè)表示設(shè)備繞x軸旋轉(zhuǎn)(范圍在-180到180之間)的數(shù)字语御,從前到后的方向?yàn)檎较?/p>

  • gamma:double峻贮;一個(gè)表示設(shè)備繞y軸旋轉(zhuǎn)(范圍在-90到90之間)的數(shù)字,從左向右為正方向应闯。

  • absolute:boolean纤控;表示該設(shè)備是否提供絕對(duì)定位數(shù)據(jù) (這個(gè)數(shù)據(jù)是關(guān)于地球的坐標(biāo)系) 或者使用了由設(shè)備決定的專門的坐標(biāo)系.

  • devicemotion:關(guān)于設(shè)備在瀏覽頁面時(shí)的位置和方向的改變速度的信息;同樣Safari不支持

devicemotionListener: function(e, t) {
    var n = e.round2(t.acceleration.x)
      , i = e.round2(t.acceleration.y)
      , e = e.round2(t.acceleration.z)
      , t = (t.rotationRate,
    t.interval);
    if (null !== n && null !== i && null !== e && (1 < Math.abs(n) || 1 < Math.abs(i) || 1 < Math.abs(e)))
        return ["dm", n, i, e, t]
}

  • acceleration.x/acceleration.y/acceleration.z: double碉纺;x, y, z方向上的加速度信息
  • rotationRate.alpha/rotationRate.beta/rotationRate.gamma: double船万;三個(gè)方向上旋轉(zhuǎn)的加速度信息
  • Interval: integer;返回從底層硬件獲取數(shù)據(jù)的時(shí)間間隔(單位:毫秒)骨田」⒌迹可以使用它來確定運(yùn)動(dòng)事件的粒度

其他公共信息

getTimestamp: function() {
    return "performance"in window && "now"in window.performance ? this.round(performance.now(), 3) : (new Date).getTime() - 1e3 * this.startedAt
},

getPassiveSupported: function() {
    let t = !1;
    try {
        var e = {
            get passive() {
                return !(t = !0)
            }
        };
        window.addEventListener("test", null, e),
        window.removeEventListener("test", null, e)
    } catch (e) {
        t = !1
    }
    return t
},

  • Timestamp:觸發(fā)時(shí)間戳,可以看到此處優(yōu)先使用window.performance.now()函數(shù)
  • PassiveSupported:用于檢查addEventlistener時(shí)是否支持使用passive模式:設(shè)置為true時(shí)盛撑,可以優(yōu)化收集滾屏事件的性能碎节,可查看https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener#%E4%BD%BF%E7%94%A8_passive_%E6%94%B9%E5%96%84%E7%9A%84%E6%BB%9A%E5%B1%8F%E6%80%A7%E8%83%BD
  • event.isTrusted:boolean捧搞;當(dāng)事件是由用戶行為生成的時(shí)候抵卫,這個(gè)屬性的值為 true ,而當(dāng)事件是由腳本創(chuàng)建胎撇、修改介粘、通過 [EventTarget.dispatchEvent()](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/dispatchEvent) 派發(fā)的時(shí)候,這個(gè)屬性的值為 false 晚树。

收集

開始recording:

Record接口提供開始行為記錄收集

getFrameHandler: function(n, i) {
    return function(e) {
        var t = i(n, e)
          , e = 1 == e.isTrusted ? 1 : 0
          , t = t.concat([e, n.getTimestamp()]);
        n.frames.push(t),
        n.pdFlag && n.frames.length >= n.push_after && (e = new Event("musPushData"),
        window.dispatchEvent(e),
        n.pdFlag = !1),
        n.onFrame && n.onFrame instanceof Function && n.onFrame(t)
    }
},
record: function() {
    if (!this.recording) {
        0 == this.startedAt && (this.startedAt = (new Date).getTime() / 1e3),
        document.scrollingElement && this.frames.push(["s", this.round2(document.scrollingElement.scrollLeft), this.round2(document.scrollingElement.scrollTop), this.getTimestamp()]);
        for (var e = 0; e < this.recordedEvents.length; e++) {
            var t = this.recordedEvents[e]
              , n = "scroll" === t
              , i = null
              , i = this.onlyWindowEvent.includes(t) && this.listenNode !== window ? window : this.listenNode;
            "visibilitychange" === t && (i = document);
            var a = this.passiveSupported ? {
                passive: !0,
                capture: n
            } : n
              , n = this.getFrameHandler(this, this[t + "Listener"]);
            this.eventListenerParams[t] = [i, t, n, a],
            i.addEventListener(t, n, a)
        }
        this.recording = !0
    }
},

本段代碼主要用來逐一注冊事件的listener(Line27-29):

  • 記錄開始時(shí)間 (Line 15)
  • 當(dāng)開始記錄時(shí)會(huì)首先記錄一次當(dāng)前滾動(dòng)條的位置(Line 16)
  • addEventListenercapture設(shè)置為true是用來阻止事件向上冒泡的姻采,只有對(duì)scroll阻止冒泡:例如針對(duì)一個(gè)iframe開啟了scroll listener,該事件不會(huì)觸發(fā)window側(cè)scroll listener(Line19)
  • onlyWindowEvent主要記錄只有window擁有的事件爵憎,由于該腳本支持設(shè)置監(jiān)聽DOM中某個(gè)node的event慨亲,所以此時(shí)若監(jiān)聽node非window則應(yīng)該去對(duì)應(yīng)監(jiān)聽window下的事件,即運(yùn)行到29行時(shí)宝鼓,i == window(Line 21)
  • 優(yōu)先使用passive模式進(jìn)行監(jiān)聽(Line 23)
  • 使用了**eventListenerParams**列表來保存了所有監(jiān)聽的事件刑棵,用于后續(xù)stop,該條值得學(xué)習(xí)
  • Line 4 - 5愚铡,每次收集都包含的公共信息
  • 可以設(shè)置push_after來控制收集多少條信息后觸發(fā)上報(bào)蛉签,所有收集的信息沒有分類,全部放在frame列表中沥寥;觸發(fā)上報(bào)的本質(zhì)是通過dispatchEvent觸發(fā)一個(gè)事件碍舍,該事件的處理函數(shù)將發(fā)起上報(bào),后面將講述具體觸發(fā)上報(bào)的時(shí)機(jī) (Line 7)
  • recording設(shè)置為1邑雅,表示開始數(shù)據(jù)收集

Stop

stop: function() {
    for (var e in this.finishedAt = (new Date).getTime() / 1e3,
    this.eventListenerParams) {
        var t = this.eventListenerParams[e];
        t[0].removeEventListener(t[1], t[2], t[3])
    }
    this.recording = !1
},

記錄下停止的時(shí)間后片橡,將record時(shí)記錄的事件全部remove掉,recording置為0表示當(dāng)前未收集數(shù)據(jù)

上報(bào)觸發(fā)時(shí)機(jī)

以下事件觸發(fā)時(shí)淮野,將發(fā)起數(shù)據(jù)上報(bào)捧书;其中"musPushData"事件即為上文描述的主動(dòng)控制收集多少條數(shù)據(jù)后進(jìn)行上報(bào)

document.addEventListener("visibilitychange", function(e) {
    "hidden" === document.visibilityState && (t = !0,
    i("vc"))
}),
window.addEventListener("pagehide", function(e) {
    !1 === t && (t = !0,
    i("ph"))
}),
window.addEventListener("beforeunload", function(e) {
    !1 === t && (t = !0,
    i("bu"))
}),
window.addEventListener("unload", function(e) {
    !1 === t && (t = !0,
    i("un"))
}),
window.addEventListener("musPushData", function(e) {
    i("pd"),
    mus.pdFlag = !0
})

DeviceData收集

該腳本同樣會(huì)收集當(dāng)前瀏覽器的信息狂塘,此處只列出部分值得學(xué)習(xí)的部分

Sayswho

用于識(shí)別當(dāng)前瀏覽器及其版本;通常會(huì)注冊在navigator中鳄厌,非標(biāo)準(zhǔn)接口荞胡;參考代碼:

navigator.sayswho= (function(){
    var ua= navigator.userAgent, tem, 
    M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
    if(/trident/i.test(M[1])){
        tem=  /\brv[ :]+(\d+)/g.exec(ua) || [];
        return 'IE '+(tem[1] || '');
    }
    if(M[1]=== 'Chrome'){
        tem= ua.match(/\b(OPR|Edge)\/(\d+)/);
        if(tem!= null) return tem.slice(1).join(' ').replace('OPR', 'Opera');
    }
    M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
    if((tem= ua.match(/version\/(\d+)/i))!= null) M.splice(1, 1, tem[1]);
    return M.join(' ');
})();

console.log(navigator.sayswho);

我們可以用此條快速解決UA解析版本的問題

String.prototype.toSource異常檢測

主流瀏覽器都會(huì)發(fā)生異常,除非是特別低版本的瀏覽器了嚎,可以快速定位低版本瀏覽器泪漂,參考代碼:

getErrorFF: function() {
    try {
        throw "a"
    } catch (e) {
        try {
            return e.toSource(),
            !0
        } catch (e) {
            return !1
        }
    }
},

Audio/Video解碼能力測試

利用canPlayType接口,若大概率可以播放歪泳,則返回"probably"萝勤,若確定無能力則返回空字符串; 不同的主流瀏覽器及版本會(huì)有比較顯著的特性,低版本瀏覽器將全部為空

audioCodecs: function() {
    var e = document.createElement("audio")
      , t = {}
      , n = {
        ogg: 'audio/ogg; codecs="vorbis"',
        mp3: "audio/mpeg;",
        wav: 'audio/wav; codecs="1"',
        m4a: "audio/x-m4a;",
        aac: "audio/aac;"
    };
    if (e.canPlayType)
        for (var i in n)
            t[i] = e.canPlayType(n[i]);
    return t
},
videoCodecs: function() {
    var e = document.createElement("video")
      , t = {}
      , n = {
        ogg: 'video/ogg; codecs="theora"',
        h264: 'video/mp4; codecs="avc1.42E01E"',
        webm: 'video/webm; codecs="vp8, vorbis"',
        mpeg4v: 'video/mp4; codecs="mp4v.20.8, mp4a.40.2"',
        mpeg4a: 'video/mp4; codecs="mp4v.20.240, mp4a.40.2"',
        theora: 'video/x-matroska; codecs="theora, vorbis"'
    };
    if (e.canPlayType)
        for (var i in n)
            t[i] = e.canPlayType(n[i]);
    return t
},

window.eval hook檢測

不同瀏覽器長度會(huì)有所不同呐伞,firefox為37敌卓,chrome類的為33,同時(shí)eval中會(huì)包含'native code'關(guān)鍵字

u.deviceData.emptyEvalLength = eval.toString().length

網(wǎng)絡(luò)相關(guān)檢測

僅chrome支持伶氢,獲取網(wǎng)絡(luò)環(huán)境信息

navigator && navigator.connection && (r = navigator.connection,
u.deviceData.connection = {
    effectiveType: r.effectiveType,
    rtt: r.rtt,
    downlink: r.downlink
})

webAssembly能力檢測

本條是在查閱資料過程中發(fā)現(xiàn)了還有類似功能的一個(gè)開源項(xiàng)目friendly challenge:GitHub - FriendlyCaptcha/friendly-challenge: The widget and docs for the proof of work challenge use趟径,其中發(fā)現(xiàn)的一個(gè)檢測點(diǎn);關(guān)于該項(xiàng)目一些相關(guān)點(diǎn)后續(xù)可以再總結(jié)

檢測方法其實(shí)比較簡單癣防,使用一串可以被編譯的字串蜗巧,使用webAssembly.compile進(jìn)行編譯,嘗試捕獲異常蕾盯,若捕獲則檢測失斈灰佟:

const A = WebAssembly.compile(function(A) {
    const C = A.length;
    let t = 3 * C >>> 2;
    A.charCodeAt(C - 1) === I && t--, A.charCodeAt(C - 2) === I && t--;
    const B = new Uint8Array(t);
    for (let I = 0, t = 0; I < C; I += 4) {
        const C = g[A.charCodeAt(I + 0)],
            Q = g[A.charCodeAt(I + 1)],
            e = g[A.charCodeAt(I + 2)],
            r = g[A.charCodeAt(I + 3)];
        B[t++] = C << 2 | Q >> 4, B[t++] = (15 & Q) << 4 | e >> 2, B[t++] = (3 & e) << 6 | 63 & r
    }
    return B
}("一個(gè)base64編碼的可編譯webAssembly源碼"))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市级遭,隨后出現(xiàn)的幾起案子望拖,更是在濱河造成了極大的恐慌,老刑警劉巖挫鸽,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件说敏,死亡現(xiàn)場離奇詭異,居然都是意外死亡掠兄,警方通過查閱死者的電腦和手機(jī)像云,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚂夕,“玉大人迅诬,你說我怎么就攤上這事⌒鲭梗” “怎么了侈贷?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我俏蛮,道長撑蚌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任搏屑,我火速辦了婚禮争涌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辣恋。我一直安慰自己亮垫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布伟骨。 她就那樣靜靜地躺著饮潦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪携狭。 梳的紋絲不亂的頭發(fā)上继蜡,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音逛腿,去河邊找鬼稀并。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鳄逾,可吹牛的內(nèi)容都是我干的稻轨。 我是一名探鬼主播灵莲,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼雕凹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了政冻?” 一聲冷哼從身側(cè)響起枚抵,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎明场,沒想到半個(gè)月后汽摹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苦锨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年逼泣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舟舒。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拉庶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秃励,到底是詐尸還是另有隱情氏仗,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布夺鲜,位于F島的核電站皆尔,受9級(jí)特大地震影響呐舔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜慷蠕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一珊拼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧流炕,春花似錦杆麸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矫付,卻和暖如春舰始,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峻堰。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工讹开, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捐名。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓旦万,卻偏偏與公主長得像,于是被迫代替她去往敵國和親镶蹋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子成艘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • * iOS人機(jī)界面準(zhǔn)則 iOS設(shè)計(jì)原則、Mac版本的iPad應(yīng)用贺归、UI控件 * 應(yīng)用架構(gòu) 程序啟動(dòng)淆两、啟動(dòng)屏、模態(tài)拂酣、...
    王滋溜閱讀 687評(píng)論 0 1
  • ??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的婶熬。 ??事件剑勾,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,500評(píng)論 1 11
  • 本節(jié)介紹各種常見的瀏覽器事件。 鼠標(biāo)事件 鼠標(biāo)事件指與鼠標(biāo)相關(guān)的事件赵颅,主要有以下一些虽另。 click 事件,dblc...
    許先生__閱讀 2,446評(píng)論 0 4
  • 本章內(nèi)容:理解事件流性含、使用事件處理程序洲赵、不同的事件類型 JavaScript與HTML之間的交互是通過事件實(shí)現(xiàn)的。...
    了凡和纖風(fēng)閱讀 343評(píng)論 0 0
  • 13.1 事件流 “DOM2級(jí)事件”規(guī)定事件流包括3個(gè)階段:事件捕獲階段芝发,處于目標(biāo)階段,事件冒泡階段苛谷。事件捕獲表示...
    Elevens_regret閱讀 430評(píng)論 0 0