Skip to content

Commit

Permalink
Merge pull request #139 from prey/rebased-location
Browse files Browse the repository at this point in the history
Add native geolocation as main geoloc strategy.
  • Loading branch information
mauricioschneider committed Jul 14, 2015
2 parents 64ca335 + a192d17 commit 47b1f5d
Show file tree
Hide file tree
Showing 23 changed files with 1,340 additions and 187 deletions.
37 changes: 26 additions & 11 deletions lib/agent/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,39 @@ helpers.run_via_service = function(){

// is_greater_than("1.3.10", "1.3.9") returns true
helpers.is_greater_than = function(first, second) {
var invalid = [];
return semver_wrapper('gt', first, second);
};

[first, second].forEach(function (el, i) {
helpers.is_greater_or_equal = function(first, second) {
return semver_wrapper('gte', first, second);
};

if(!semver.valid(el)) {
function semver_wrapper(method_name, first, second) {
var valid = validate_versions([first, second], method_name);

var label = i === 0 ? "first" : "second";
invalid.push(label);
}
return valid && semver[method_name](first, second);
}

function validate_versions(versions, method_name) {
var invalid_versions = [];

versions.forEach(function(el) {
if(!semver.valid(el)) {
invalid_versions.push(el);
}
});

if (invalid.length > 0) {
first = second = "0.0.1";
exceptions.send(new Error("Cannot run is_greater_than. Invalid versions: "+ invalid + ". Values: " + first + ", " + second));
if(invalid_versions.length > 0) {
handle_version_error(method_name, invalid_versions);
return false;
}
return true;
}

return semver.gt(first, second);
};
function handle_version_error(method_name, versions) {
var err_msg = "Cannot run" + method_name + ". Invalid versions: ";
err_msg = err_msg.concat(versions.join(" "));
exceptions.send(new Error(err_msg));
}

module.exports = helpers;
Binary file added lib/agent/providers/geo/darwin/bin/whereami
Binary file not shown.
43 changes: 43 additions & 0 deletions lib/agent/providers/geo/darwin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
var join = require('path').join,
child_process = require('child_process'),
common = require('../../../common'),
system = common.system,
gte = common.helpers.is_greater_or_equal;

// whereami bin from https://github.com/robmathers/WhereAmI
var bin = join(__dirname, 'bin', 'whereami');

var get_using_corelocation = function(cb) {
child_process.exec(bin, function(err, out) {
if (err) return cb(err);

if (!out.toString().match('Latitude'))
return cb(new Error('Unable to get geoposition data using CoreLocation.'));

var match, str = out.toString();

var res = {
lat: str.match(/Latitude: (.+)/)[1],
lng: str.match(/Longitude: (.+)/)[1]
}

if (match = str.match(/\nAccuracy.*: (.+)/)) {
res.accuracy = match[1];
} else if (match = str.match(/Horizontal Accuracy.*: (.+)/)) {
res.accuracy = match[1];
res.vertical_accuracy = str.match(/Vertical Accuracy.*: (.+)/)[1];
res.altitude = str.match(/Altitude.*: (.+)/)[1];
}

cb(null, res);
});
}

exports.get_location = function(cb) {
system.get_os_version(function(err, version) {
if (version && gte(version, "10.6.0"))
return get_using_corelocation(cb);

cb(new Error('CoreLocation not suppored in OSX ' + version))
});
}
87 changes: 29 additions & 58 deletions lib/agent/providers/geo/index.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,41 @@
"use strict";

//////////////////////////////////////////
// Prey JS Geo Module
// (c) 2011 - Fork Ltd.
// by Tomas Pollak - http://forkhq.com
// GPLv3 Licensed
//////////////////////////////////////////
var strategies = require('./strategies'),
logger = require('../../common').logger.prefix('geo');

var client = require('needle'),
get_agent = require('random-ua').generate,
providers = require('./../../providers');
function log_error(err, strategy) {
logger.debug("Error getting location using " + strategy + " strategy: " + err);
}

var process_response = function(body, cb){
var coords;
exports.get_location = function(cb) {

if (typeof body === 'object') {
coords = body;
} else {
try {
coords = JSON.parse(body);
} catch(e) {
return cb(e);
}
}

if (!coords.location || (!coords.location.lat && !coords.location.latitude))
return cb(new Error("Couldn't get any geoposition data. Try moving around a bit."));

var data = {
lat: coords.location.lat || coords.location.latitude,
lng: coords.location.lng || coords.location.longitude,
accuracy: coords.accuracy || coords.location.accuracy,
method: 'wifi'
};

cb(null, data);
};


exports.send_data = function(list, cb){

var aps = [];
strategies.native(native_cb);

list.slice(0, 30).forEach(function(ap){
var str = 'wifi=mac:' + ap.mac_address + '|ssid:' + encodeURIComponent(ap.ssid);
str += '|ss:' + ap.signal_strength;
aps.push(str);
});

var opts = { user_agent: get_agent() },
url = 'https://maps.googleapis.com/maps/api/browserlocation/json?',
query = url + 'browser=true&sensor=true&' + aps.join('&');
function native_cb(err, res) {
if (err) {
log_error(err, "native");
return strategies.google(google_cb);
}

client.get(query, opts, function(err, resp, body) {
if (err) return cb(err);
process_response(body, cb);
});
return cb(null, res);
}

};
function google_cb(err, res) {
if (err) {
log_error(err, "google");
return strategies.geoip(geoip_cb);
}

exports.get_location = function(callback){
return cb(null, res);
}

providers.get('access_points_list', function(err, list) {
if (err) return callback(err);
function geoip_cb(err, res) {
if (err) {
log_error(err, "geoip");
return cb(err);
}

exports.send_data(list, callback);
});
return cb(null, res);
}

};
}
82 changes: 82 additions & 0 deletions lib/agent/providers/geo/linux/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// geolocation provider using geoclue service

var exec = require('child_process').exec;

/*
valid dbus reply format:
int32 # number of fields
int32 # timestamp
double # lat
double # lng
double # altitude
struct { # accuracy
int32 3
double 0
double 0
}
*/

function parse(out, cb) {
var matches = out.match(/double ([\d\.-]+)/g);
if (matches < 2)
return cb(new Error('Unable to get location.'))

var res = {
lat: parseFloat(matches[0].replace('double ', '')),
lng: parseFloat(matches[1].replace('double ', '')),
altitude: parseFloat(matches[2].replace('double ', ''))
}

cb(null, res);
}

function get_command_one(provider) {
var bin = 'dbus-send',
service = 'org.freedesktop.Geoclue.Providers.' + provider,
path = '/org/freedesktop/Geoclue/Providers/' + provider,
command = 'org.freedesktop.Geoclue.Position.GetPosition';

return [bin, '--print-reply', '--dest=' + service, path, command].join(' ');
}

function geoclue_one(provider, cb) {
var cmd = get_command_one(provider);

exec(cmd, function(err, out) {
if (err) return cb(err);

parse(out, cb);
})
}

function get_command_two() {
var bin = 'dbus-send',
service = 'org.freedesktop.GeoClue2',
path = '/org/freedesktop/GeoClue2',
command = 'org.freedesktop.GeoClue2.Location';

return [bin, '--print-reply', '--dest=' + service, path, command].join(' ');
}

function geoclue_two(cb) {
exec(get_command_two, function(err, out) {
if (err) return cb(err);

parse(out, cb);
})
}

exports.get_location = function(cb) {
geoclue_two(function(err, res) {
if (res) return cb(null, res);

geoclue_one('Skyhook', function(err, res) {
if (res) return cb(null, res);

geoclue_one('UbuntuGeoIP', cb);
});
})
}
116 changes: 116 additions & 0 deletions lib/agent/providers/geo/strategies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use strict"

var join = require('path').join,
needle = require('needle'),
get_agent = require('random-ua').generate,
platform = require(join(__dirname, process.platform)),
providers = require('./../../providers'),
logger = require('../../common').logger.prefix('geo');

function geoip(cb) {
logger.debug("Getting location via geoip");

needle.get('http://ipinfo.io/geo', function(err, resp, body) {
if (!body || !body.loc) {
return cb(err || new Error('Unable to get location from IP.'));
}

logger.debug("Got location via geoip");

var res = {
lat: parseFloat(body.loc.split(',')[0]),
lng: parseFloat(body.loc.split(',')[1]),
method: 'geoip'
}

cb(null, res);
});
}

function google(cb) {
logger.debug("Getting location via google api");

providers.get('access_points_list', function(err, list) {
if (err) return cb(err);

send_data(list, cb);
});

function send_data(list, cb) {

logger.debug("Sending AP data to Google API");

var aps = [];

list.slice(0, 30).forEach(function(ap) {
var str = 'wifi=mac:' + ap.mac_address + '|ssid:' + encodeURIComponent(ap.ssid);
str += '|ss:' + ap.signal_strength;
aps.push(str);
});

var opts = {
user_agent: get_agent()
},
url = 'https://maps.googleapis.com/maps/api/browserlocation/json?',
query = url + 'browser=true&sensor=true&' + aps.join('&');

needle.get(query, opts, function(err, resp, body) {
if (err) return cb(err);
process_response(body, cb);
});

}

function process_response(body, cb) {
logger.debug("Processing Google API response");

var coords;

if (typeof body === 'object') {
coords = body;
} else {
try {
coords = JSON.parse(body);
} catch (e) {
return cb(e);
}
}

if (!coords.location || (!coords.location.lat && !coords.location.latitude))
return cb(new Error("Couldn't get any geoposition data. Try moving around a bit."));

logger.debug("Got location via Google API");

var data = {
lat: coords.location.lat || coords.location.latitude,
lng: coords.location.lng || coords.location.longitude,
accuracy: coords.accuracy || coords.location.accuracy,
method: 'wifi'
};

return cb(null, data);
}
}

function geonative(cb) {
logger.debug("Getting location via native geoloc");

platform.get_location(function(err, res) {
if (err) {
return cb(err);
}

logger.debug("Got location via native geoloc");

// Avoid adding property in each native geoloc implementation
res.method = 'geonative';

return cb(null, res);
});
}

module.exports = {
'geoip': geoip,
'google': google,
'native': geonative
};
Loading

0 comments on commit 47b1f5d

Please sign in to comment.