很久沒有寫博客了舌剂,主要是最近換了個(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í)获茬;
用戶行為數(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)角. 由
radiusX
和radiusY
描述的正方向的橢圓盗迟,通過順時(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)簽頁中,并且窗口沒有最小化.
-
- document.visibilityState:String嫉称;返回document的可見性, 即當(dāng)前可見元素的上下文環(huán)境. 由此可以知道當(dāng)前文檔(即為頁面)是在背后, 或是不可見的隱藏的標(biāo)簽頁侦镇,或者(正在)預(yù)渲染.可用的值如下:
'hidden
' : 此時(shí)頁面對(duì)用戶不可見. 即文檔處于背景標(biāo)簽頁或者窗口處于最小化狀態(tài)织阅,或者操作系統(tǒng)正處于 '鎖屏狀態(tài)' .'prerender'
: 頁面此時(shí)正在渲染中, 因此是不可見的 (considered hidden for purposes ofdocument.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)
-
addEventListener
的capture
設(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源碼"))