Skip to content

Instantly share code, notes, and snippets.

@yijian166
Created July 2, 2015 06:31
Show Gist options
  • Save yijian166/c71085bfbf3159e2ddcc to your computer and use it in GitHub Desktop.
Save yijian166/c71085bfbf3159e2ddcc to your computer and use it in GitHub Desktop.
浏览器检测
//https://github.com/hotoo/detector/blob/master/detector.js
var detector = {};
var NA_VERSION = "-1";
var win = this;
var external;
var re_msie = /\b(?:msie |ie |trident\/[0-9].*rv[ :])([0-9.]+)/;
var re_blackberry_10 = /\bbb10\b.+?\bversion\/([\d.]+)/;
var re_blackberry_6_7 = /\bblackberry\b.+\bversion\/([\d.]+)/;
var re_blackberry_4_5 = /\bblackberry\d+\/([\d.]+)/;
function toString(object){
return Object.prototype.toString.call(object);
}
function isObject(object){
return toString(object) === "[object Object]";
}
function isFunction(object){
return toString(object) === "[object Function]";
}
function each(object, factory, argument){
for(var i=0,b,l=object.length; i<l; i++){
if(factory.call(object, object[i], i) === false){break;}
}
}
// 硬件设备信息识别表达式。
// 使用数组可以按优先级排序。
var DEVICES = [
["nokia", function(ua){
// 不能将两个表达式合并,因为可能出现 "nokia; nokia 960"
// 这种情况下会优先识别出 nokia/-1
if(ua.indexOf("nokia ") !== -1){
return /\bnokia ([0-9]+)?/;
}else{
return /\bnokia([a-z0-9]+)?/;
}
}],
// 三星有 Android 和 WP 设备。
["samsung", function(ua){
if(ua.indexOf("samsung") !== -1){
return /\bsamsung(?:[ \-](?:sgh|gt|sm))?-([a-z0-9]+)/;
}else{
return /\b(?:sgh|sch|gt|sm)-([a-z0-9]+)/;
}
}],
["wp", function(ua){
return ua.indexOf("windows phone ") !== -1 ||
ua.indexOf("xblwp") !== -1 ||
ua.indexOf("zunewp") !== -1 ||
ua.indexOf("windows ce") !== -1;
}],
["pc", "windows"],
["ipad", "ipad"],
// ipod 规则应置于 iphone 之前。
["ipod", "ipod"],
["iphone", /\biphone\b|\biph(\d)/],
["mac", "macintosh"],
// 小米
["mi", /\bmi[ \-]?([a-z0-9 ]+(?= build|\)))/],
// 红米
['hongmi', /\bhm[ \-]?([a-z0-9]+)/],
["aliyun", /\baliyunos\b(?:[\-](\d+))?/],
["meizu", function(ua) {
return ua.indexOf("meizu") >= 0 ?
/\bmeizu[\/ ]([a-z0-9]+)\b/
:
/\bm([0-9x]{1,3})\b/
}],
["nexus", /\bnexus ([0-9s.]+)/],
["huawei", function(ua){
var re_mediapad = /\bmediapad (.+?)(?= build\/huaweimediapad\b)/;
if(ua.indexOf("huawei-huawei") !== -1){
return /\bhuawei\-huawei\-([a-z0-9\-]+)/;
}else if(re_mediapad.test(ua)){
return re_mediapad;
}else{
return /\bhuawei[ _\-]?([a-z0-9]+)/;
}
}],
["lenovo", function(ua){
if(ua.indexOf("lenovo-lenovo") !== -1){
return /\blenovo\-lenovo[ \-]([a-z0-9]+)/;
}else{
return /\blenovo[ \-]?([a-z0-9]+)/;
}
}],
// 中兴
["zte", function(ua){
if(/\bzte\-[tu]/.test(ua)){
return /\bzte-[tu][ _\-]?([a-su-z0-9\+]+)/;
}else{
return /\bzte[ _\-]?([a-su-z0-9\+]+)/;
}
}],
// 步步高
["vivo", /\bvivo(?: ([a-z0-9]+))?/],
["htc", function(ua){
if(/\bhtc[a-z0-9 _\-]+(?= build\b)/.test(ua)){
return /\bhtc[ _\-]?([a-z0-9 ]+(?= build))/;
}else{
return /\bhtc[ _\-]?([a-z0-9 ]+)/;
}
}],
["oppo", /\boppo[_]([a-z0-9]+)/],
["konka", /\bkonka[_\-]([a-z0-9]+)/],
["sonyericsson", /\bmt([a-z0-9]+)/],
["coolpad", /\bcoolpad[_ ]?([a-z0-9]+)/],
["lg", /\blg[\-]([a-z0-9]+)/],
["android", /\bandroid\b|\badr\b/],
["blackberry", function(ua){
if (ua.indexOf("blackberry") >= 0) {
return /\bblackberry\s?(\d+)/;
}
return "bb10";
}]
];
// 操作系统信息识别表达式
var OS = [
["wp", function(ua){
if(ua.indexOf("windows phone ") !== -1){
return /\bwindows phone (?:os )?([0-9.]+)/;
}else if(ua.indexOf("xblwp") !== -1){
return /\bxblwp([0-9.]+)/;
}else if(ua.indexOf("zunewp") !== -1){
return /\bzunewp([0-9.]+)/;
}
return "windows phone";
}],
["windows", /\bwindows nt ([0-9.]+)/],
["macosx", /\bmac os x ([0-9._]+)/],
["ios", function(ua){
if(/\bcpu(?: iphone)? os /.test(ua)){
return /\bcpu(?: iphone)? os ([0-9._]+)/;
}else if(ua.indexOf("iph os ") !== -1){
return /\biph os ([0-9_]+)/;
}else{
return /\bios\b/;
}
}],
["yunos", /\baliyunos ([0-9.]+)/],
["android", function(ua){
if(ua.indexOf("android") >= 0){
return /\bandroid[ \/-]?([0-9.x]+)?/;
}else if(ua.indexOf("adr") >= 0){
if(ua.indexOf("mqqbrowser") >= 0){
return /\badr[ ]\(linux; u; ([0-9.]+)?/;
}else{
return /\badr(?:[ ]([0-9.]+))?/;
}
}
return "android";
//return /\b(?:android|\badr)(?:[\/\- ](?:\(linux; u; )?)?([0-9.x]+)?/;
}],
["chromeos", /\bcros i686 ([0-9.]+)/],
["linux", "linux"],
["windowsce", /\bwindows ce(?: ([0-9.]+))?/],
["symbian", /\bsymbian(?:os)?\/([0-9.]+)/],
["blackberry", function(ua){
var m = ua.match(re_blackberry_10) ||
ua.match(re_blackberry_6_7) ||
ua.match(re_blackberry_4_5);
return m ? {version: m[1]} : "blackberry";
}]
];
// 解析使用 Trident 内核的浏览器的 `浏览器模式` 和 `文档模式` 信息。
// @param {String} ua, userAgent string.
// @return {Object}
function IEMode(ua){
if(!re_msie.test(ua)){return null;}
var m,
engineMode, engineVersion,
browserMode, browserVersion,
compatible=false;
// IE8 及其以上提供有 Trident 信息,
// 默认的兼容模式,UA 中 Trident 版本不发生变化。
if(ua.indexOf("trident/") !== -1){
m = /\btrident\/([0-9.]+)/.exec(ua);
if(m && m.length>=2){
// 真实引擎版本。
engineVersion = m[1];
var v_version = m[1].split(".");
v_version[0] = parseInt(v_version[0], 10) + 4;
browserVersion = v_version.join(".");
}
}
m = re_msie.exec(ua);
browserMode = m[1];
var v_mode = m[1].split(".");
if("undefined" === typeof browserVersion){
browserVersion = browserMode;
}
v_mode[0] = parseInt(v_mode[0], 10) - 4;
engineMode = v_mode.join(".");
if("undefined" === typeof engineVersion){
engineVersion = engineMode;
}
return {
browserVersion: browserVersion,
browserMode: browserMode,
engineVersion: engineVersion,
engineMode: engineMode,
compatible: engineVersion !== engineMode
};
}
// 针对同源的 TheWorld 和 360 的 external 对象进行检测。
// @param {String} key, 关键字,用于检测浏览器的安装路径中出现的关键字。
// @return {Undefined,Boolean,Object} 返回 undefined 或 false 表示检测未命中。
function checkTW360External(key){
if(!external){return;} // return undefined.
try{
// 360安装路径:
// C:%5CPROGRA~1%5C360%5C360se3%5C360SE.exe
var runpath = external.twGetRunPath.toLowerCase();
// 360SE 3.x ~ 5.x support.
// 暴露的 external.twGetVersion 和 external.twGetSecurityID 均为 undefined。
// 因此只能用 try/catch 而无法使用特性判断。
var security = external.twGetSecurityID(win);
var version = external.twGetVersion(security);
if(runpath && runpath.indexOf(key) === -1){return false;}
if(version){return {version: version};}
}catch(ex){}
}
var ENGINE = [
["trident", re_msie],
["blink", function(ua){
return "chrome" in win && "CSS" in win && /\bapplewebkit[\/]?([0-9.+]+)/;
}],
["webkit", /\bapplewebkit[\/]?([0-9.+]+)/],
["gecko", function(ua){
var match;
if (match = ua.match(/\brv:([\d\w.]+).*\bgecko\/(\d+)/)) {
return {
version: match[1] + "." + match[2]
}
}
}],
["presto", /\bpresto\/([0-9.]+)/],
["androidwebkit", /\bandroidwebkit\/([0-9.]+)/],
["coolpadwebkit", /\bcoolpadwebkit\/([0-9.]+)/],
["u2", /\bu2\/([0-9.]+)/],
["u3", /\bu3\/([0-9.]+)/]
];
var BROWSER = [
// Sogou.
["sogou", function(ua){
if (ua.indexOf("sogoumobilebrowser") >= 0) {
return /sogoumobilebrowser\/([0-9.]+)/
} else if (ua.indexOf("sogoumse") >= 0){
return true;
}
return / se ([0-9.x]+)/;
}],
// TheWorld (世界之窗)
// 由于裙带关系,TheWorld API 与 360 高度重合。
// 只能通过 UA 和程序安装路径中的应用程序名来区分。
// TheWorld 的 UA 比 360 更靠谱,所有将 TheWorld 的规则放置到 360 之前。
["theworld", function(ua){
var x = checkTW360External("theworld");
if(typeof x !== "undefined"){return x;}
return "theworld";
}],
// 360SE, 360EE.
["360", function(ua) {
var x = checkTW360External("360se");
if(typeof x !== "undefined"){return x;}
if(ua.indexOf("360 aphone browser") !== -1){
return /\b360 aphone browser \(([^\)]+)\)/;
}
return /\b360(?:se|ee|chrome|browser)\b/;
}],
// Maxthon
["maxthon", function(ua){
try{
if(external && (external.mxVersion || external.max_version)){
return {
version: external.mxVersion || external.max_version
};
}
}catch(ex){}
return /\b(?:maxthon|mxbrowser)(?:[ \/]([0-9.]+))?/;
}],
["qq", /\bm?qqbrowser\/([0-9.]+)/],
["green", "greenbrowser"],
["tt", /\btencenttraveler ([0-9.]+)/],
["liebao", function(ua){
if (ua.indexOf("liebaofast") >= 0){
return /\bliebaofast\/([0-9.]+)/;
}
if(ua.indexOf("lbbrowser") === -1){return false;}
var version;
try{
if(external && external.LiebaoGetVersion){
version = external.LiebaoGetVersion();
}
}catch(ex){}
return {
version: version || NA_VERSION
};
}],
["tao", /\btaobrowser\/([0-9.]+)/],
["coolnovo", /\bcoolnovo\/([0-9.]+)/],
["saayaa", "saayaa"],
// 有基于 Chromniun 的急速模式和基于 IE 的兼容模式。必须在 IE 的规则之前。
["baidu", /\b(?:ba?idubrowser|baiduhd)[ \/]([0-9.x]+)/],
// 后面会做修复版本号,这里只要能识别是 IE 即可。
["ie", re_msie],
["mi", /\bmiuibrowser\/([0-9.]+)/],
// Opera 15 之后开始使用 Chromniun 内核,需要放在 Chrome 的规则之前。
["opera", function(ua){
var re_opera_old = /\bopera.+version\/([0-9.ab]+)/;
var re_opera_new = /\bopr\/([0-9.]+)/;
return re_opera_old.test(ua) ? re_opera_old : re_opera_new;
}],
["oupeng", /\boupeng\/([0-9.]+)/],
["yandex", /yabrowser\/([0-9.]+)/],
// 支付宝手机客户端
["ali-ap", function(ua){
if(ua.indexOf("aliapp") > 0){
return /\baliapp\(ap\/([0-9.]+)\)/;
}else{
return /\balipayclient\/([0-9.]+)\b/;
}
}],
// 支付宝平板客户端
["ali-ap-pd", /\baliapp\(ap-pd\/([0-9.]+)\)/],
// 支付宝商户客户端
["ali-am", /\baliapp\(am\/([0-9.]+)\)/],
// 淘宝手机客户端
["ali-tb", /\baliapp\(tb\/([0-9.]+)\)/],
// 淘宝平板客户端
["ali-tb-pd", /\baliapp\(tb-pd\/([0-9.]+)\)/],
// 天猫手机客户端
["ali-tm", /\baliapp\(tm\/([0-9.]+)\)/],
// 天猫平板客户端
["ali-tm-pd", /\baliapp\(tm-pd\/([0-9.]+)\)/],
// UC 浏览器,可能会被识别为 Android 浏览器,规则需要前置。
// UC 桌面版浏览器携带 Chrome 信息,需要放在 Chrome 之前。
["uc", function(ua){
if(ua.indexOf("ucbrowser/") >= 0){
return /\bucbrowser\/([0-9.]+)/;
} else if(ua.indexOf("ubrowser/") >= 0){
return /\bubrowser\/([0-9.]+)/;
}else if(/\buc\/[0-9]/.test(ua)){
return /\buc\/([0-9.]+)/;
}else if(ua.indexOf("ucweb") >= 0){
// `ucweb/2.0` is compony info.
// `UCWEB8.7.2.214/145/800` is browser info.
return /\bucweb([0-9.]+)?/;
}else{
return /\b(?:ucbrowser|uc)\b/;
}
}],
["chrome", / (?:chrome|crios|crmo)\/([0-9.]+)/],
// Android 默认浏览器。该规则需要在 safari 之前。
["android", function(ua){
if(ua.indexOf("android") === -1){return;}
return /\bversion\/([0-9.]+(?: beta)?)/;
}],
["blackberry", function(ua){
var m = ua.match(re_blackberry_10) ||
ua.match(re_blackberry_6_7) ||
ua.match(re_blackberry_4_5);
return m ? {version: m[1]} : "blackberry";
}],
["safari", /\bversion\/([0-9.]+(?: beta)?)(?: mobile(?:\/[a-z0-9]+)?)? safari\//],
// 如果不能被识别为 Safari,则猜测是 WebView。
["webview", /\bcpu(?: iphone)? os (?:[0-9._]+).+\bapplewebkit\b/],
["firefox", /\bfirefox\/([0-9.ab]+)/],
["nokia", /\bnokiabrowser\/([0-9.]+)/]
];
// UserAgent Detector.
// @param {String} ua, userAgent.
// @param {Object} expression
// @return {Object}
// 返回 null 表示当前表达式未匹配成功。
function detect(name, expression, ua){
var expr = isFunction(expression) ? expression.call(null, ua) : expression;
if(!expr){return null;}
var info = {
name: name,
version: NA_VERSION,
codename: ""
};
var t = toString(expr);
if(expr === true){
return info;
}else if(t === "[object String]"){
if(ua.indexOf(expr) !== -1){
return info;
}
}else if(isObject(expr)){ // Object
if(expr.hasOwnProperty("version")){
info.version = expr.version;
}
return info;
}else if(expr.exec){ // RegExp
var m = expr.exec(ua);
if(m){
if(m.length >= 2 && m[1]){
info.version = m[1].replace(/_/g, ".");
}else{
info.version = NA_VERSION;
}
return info;
}
}
}
var na = {name:"na", version:NA_VERSION};
// 初始化识别。
function init(ua, patterns, factory, detector){
var detected = na;
each(patterns, function(pattern){
var d = detect(pattern[0], pattern[1], ua);
if(d){
detected = d;
return false;
}
});
factory.call(detector, detected.name, detected.version);
}
// 解析 UserAgent 字符串
// @param {String} ua, userAgent string.
// @return {Object}
var parse = function(ua){
ua = (ua || "").toLowerCase();
var d = {};
init(ua, DEVICES, function(name, version){
var v = parseFloat(version);
d.device = {
name: name,
version: v,
fullVersion: version
};
d.device[name] = v;
}, d);
init(ua, OS, function(name, version){
var v = parseFloat(version);
d.os = {
name: name,
version: v,
fullVersion: version
};
d.os[name] = v;
}, d);
var ieCore = IEMode(ua);
init(ua, ENGINE, function(name, version){
var mode = version;
// IE 内核的浏览器,修复版本号及兼容模式。
if(ieCore){
version = ieCore.engineVersion || ieCore.engineMode;
mode = ieCore.engineMode;
}
var v = parseFloat(version);
d.engine = {
name: name,
version: v,
fullVersion: version,
mode: parseFloat(mode),
fullMode: mode,
compatible: ieCore ? ieCore.compatible : false
};
d.engine[name] = v;
}, d);
init(ua, BROWSER, function(name, version){
var mode = version;
// IE 内核的浏览器,修复浏览器版本及兼容模式。
if(ieCore){
// 仅修改 IE 浏览器的版本,其他 IE 内核的版本不修改。
if(name === "ie"){
version = ieCore.browserVersion;
}
mode = ieCore.browserMode;
}
var v = parseFloat(version);
d.browser = {
name: name,
version: v,
fullVersion: version,
mode: parseFloat(mode),
fullMode: mode,
compatible: ieCore ? ieCore.compatible : false
};
d.browser[name] = v;
}, d);
return d;
};
// NodeJS.
if(typeof process === "object" && process.toString() === "[object process]"){
// 加载更多的规则。
var morerule = module["require"]("./morerule");
[].unshift.apply(DEVICES, morerule.DEVICES || []);
[].unshift.apply(OS, morerule.OS || []);
[].unshift.apply(BROWSER, morerule.BROWSER || []);
[].unshift.apply(ENGINE, morerule.ENGINE || []);
}else{
var userAgent = navigator.userAgent || "";
//var platform = navigator.platform || "";
var appVersion = navigator.appVersion || "";
var vendor = navigator.vendor || "";
external = win.external;
detector = parse(userAgent + " " + appVersion + " " + vendor);
}
// exports `parse()` API anyway.
detector.parse = parse;
module.exports = detector;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment