Skip to content
This repository has been archived by the owner on Jul 19, 2018. It is now read-only.

Some minor fixes to Node port + support for device, isMobile, isSpider #56

Closed
wants to merge 9 commits into from
19 changes: 14 additions & 5 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,34 @@ Usage :: [node.js][1]
---------------------
```js
var uaParser = require('ua-parser');
var ua = uaParser.parse(navigator.userAgent);
var ua = uaParser.parse("Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3");

console.log(ua.tostring());
// -> "Safari 5.0.1"
// -> "Mobile Safari 5.1"

console.log(ua.toVersionString());
// -> "5.0.1"

console.log(ua.family);
// -> "Safari"
// -> "Mobile Safari"

console.log(ua.major);
// -> 5

console.log(ua.minor);
// -> 0
// -> 1

console.log(ua.patch);
// -> 1
// -> 0

console.log(ua.os);
// -> { family: 'iOS', major: 5, minor: 1, patch: 1 }

console.log(ua.isMobile);
// -> true

console.log(ua.isSpider);
// -> false
```


Expand Down
156 changes: 127 additions & 29 deletions js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ var file = path.join(__dirname, '..', 'regexes.yaml');
var regexes = fs.readFileSync(file, 'utf8');
regexes = yaml.eval(regexes);

var mobile_agents = {};
var mobile_user_agent_families = regexes.mobile_user_agent_families.map(function(str) {
mobile_agents[str] = true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you not returning anything on purpose here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I should have just used a for-loop here. Thanks for pointing this out.

});

var mobile_os_families = regexes.mobile_os_families.map(function(str) {
mobile_agents[str] = true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you not returning anything on purpose here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I should have just used a for-loop here. Thanks for pointing this out.

});

var ua_parsers = regexes.user_agent_parsers.map(function(obj) {
var regexp = new RegExp(obj.regex),
famRep = obj.family_replacement,
majorVersionRep = obj.v1_replacement;
majorVersionRep = obj.v1_replacement,
minorVersionRep = obj.v2_replacement;

function parser(ua) {
var m = ua.match(regexp);
Expand All @@ -19,9 +29,36 @@ var ua_parsers = regexes.user_agent_parsers.map(function(obj) {
var family = famRep ? famRep.replace('$1', m[1]) : m[1];

var obj = new UserAgent(family);
obj.major = parseInt(majorVersionRep ? majorVersionRep : m[2]);
obj.minor = m[3] ? parseInt(m[3]) : null;
obj.patch = m[4] ? parseInt(m[4]) : null;

/**
* Making use of the Double Bitwise Not trick. Reference:
* http://www.slideshare.net/madrobby/extreme-javascript-performance
* http://james.padolsey.com/javascript/double-bitwise-not/
*
* All non-zero equivalents will be truthy and will be floored if float:
* ~~true; // 1
* ~~4.9; // 4
* ~~(-2.9); // -2
* null, undefined and false will be falsey:
* ~~null // 0
* ~~undefined // 0
* ~~false // 0
* For NaN and Infinity, internal ToInt32 converts it to 0.
* ~~NaN // 0
* ~~Infinity // 0
* ~~(1/0) // 0
*/

obj.major = ~~parseInt(majorVersionRep ? majorVersionRep : m[2]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a code comment stating how ~~ works with numbers and NaN

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added relevant information regarding the double bitwise not trick.

obj.minor = ~~parseInt(minorVersionRep ? minorVersionRep : m[3]);
obj.patch = ~~parseInt(m[4]);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand what the purpose of using ~~ here is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useful if you want to return major, minor, patch as integers. The reason for using ~~ along with parseInt is that parseInt can at times return NaN. ~~ makes sure you get 0 for any kind of falsey (NaN/undefined etc) output. Imagine having to get a major.minor.patch as 1.NaN.3.


if(mobile_agents.hasOwnProperty(family)) {
obj.isMobile = true;
}
if(family == "Spider") {
obj.isSpider = true;
}

return obj;
}
Expand All @@ -31,66 +68,127 @@ var ua_parsers = regexes.user_agent_parsers.map(function(obj) {

var os_parsers = regexes.os_parsers.map(function(obj) {
var regexp = new RegExp(obj.regex),
osRep = obj.os_replacement;
osRep = obj.os_replacement,
minorVersionRep = obj.os_v1_replacement,
majorVersionRep = obj.os_v2_replacement;

function parser(ua) {
var m = ua.match(regexp);

if(!m) { return null; }

var os = (osRep ? osRep : m[1]) + (m.length > 2 ? " " + m[2] : "") + (m.length > 3 ? "." + m[3] : "");
var os = {
family: osRep ? osRep.replace('$1', m[1]) : m[1],
major : ~~parseInt(majorVersionRep ? majorVersionRep : m[2]),
minor : ~~parseInt(minorVersionRep ? minorVersionRep : m[3]),
patch : ~~parseInt(m[4])
};

return os;
}

return parser;
});

var device_parsers = regexes.device_parsers.map(function(obj) {
var regexp = new RegExp(obj.regex),
deviceRep = obj.device_replacement;

function parser(ua) {
var m = ua.match(regexp);

if(!m) { return null; }

var device = deviceRep ? deviceRep.replace('$1', m[1]) : m[1];

return device;
}

return parser;
});

exports.parse = parse;
function parse(ua) {
var os, i;
for (i=0; i < ua_parsers.length; i++) {
var result = ua_parsers[i](ua);
if (result) { break; }
}
var result, os, device, i;

for (i=0; i < os_parsers.length; i++) {
os = os_parsers[i](ua);
if (os) { break; }
}
ua_parsers.some(function(u) {
if(!!(u(ua))) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for !!.

return result = u(ua);
}
});

os_parsers.some(function(o) {
if(!!(o(ua))) {
return os = o(ua);
}
});

device_parsers.some(function(d) {
if(!!(d(ua))) {
return device = d(ua);
}
});

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I don't understand why you're operating these style changes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember now as to why this was needed. Can revert back for the time being.

if(!result) { result = new UserAgent(); }

result.os = os;
if(os) {
result.os.family = os.family;
result.os.major = os.major;
result.os.minor = os.minor;
result.os.patch = os.patch;
}
result.device = device || "Other";
return result;
}

function UserAgent(family) {
this.family = family || 'Other';
}

UserAgent.prototype.toVersionString = function() {
function toVersionString() {
var output = '';
if (this.major != null) {
if (this.major) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you're changing this here either.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was only required because I was converting major, minor, patch to return integers. So if this.major = 0, it would fail the test this.major != null (would return true instead).

output += this.major;
if (this.minor != null) {
if (this.minor) {
output += '.' + this.minor;
if (this.patch != null) {
if (this.patch) {
output += '.' + this.patch;
}
}
}
return output;
};
return output;
}

UserAgent.prototype.toString = function() {
function toString() {
var suffix = this.toVersionString();
if (suffix) { suffix = ' ' + suffix; }
return this.family + suffix;
};
}

function OS() {
this.family = "Other";
this.major = 0;
this.minor = 0;
this.patch = 0;
}

OS.prototype.toVersionString = toVersionString;

OS.prototype.toString = toString;

function UserAgent(family) {
this.os = new OS();

this.family = family || 'Other';
this.major = 0;
this.minor = 0;
this.patch = 0;
this.isMobile = false;
this.isSpider = false;
}

UserAgent.prototype.toVersionString = toVersionString;

UserAgent.prototype.toString = toString;

UserAgent.prototype.toFullString = function() {
return this.toString() + (this.os ? "/" + this.os : "");
return this.toString() + (this.os.toString() ? "/" + this.os.toString() : "");
};

if (require.main === module) {
Expand Down
2 changes: 2 additions & 0 deletions regexes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ user_agent_parsers:
family_replacement: 'Swiftfox'

# Rekonq
- regex: '(rekonq)/(.*?)[.](.*?) Safari'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you can go ahead and merge in the regex fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. I've incorporated your suggestions. :) Shall I merge the fix now?

family_replacement: 'Rekonq'
- regex: 'rekonq'
family_replacement: 'Rekonq'

Expand Down