Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native geolocation as main geoloc strategy. #139

Merged
merged 25 commits into from
Jul 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1370e31
Use CoreLocation (via LocateMe binary) to get location in OSX.
tomas Sep 17, 2014
bfc2ab1
Replace LocateMe binary with whereami.
tomas Sep 17, 2014
c430eef
CoreLocation only supported above 10.6
tomas Sep 17, 2014
9cc29a4
Use os_version to determine whether to use CoreLocation or not.
tomas Sep 17, 2014
5e814df
Start moving platform-specific geolocation strategy to separate files.
tomas Oct 18, 2014
ed437ac
Removed Google Geolocation strategy. Use system native method, and fa…
tomas Oct 18, 2014
3578389
Added Linux geolocation strategy using GeoClue via dbus-send.
tomas Oct 18, 2014
fad511e
Added win32 geoloc strategy using WinRT module.
tomaspollak Oct 18, 2014
ab336f6
Include accuracy on WinRT geoposition success.
tomas Jun 11, 2015
d1f0bc9
Add strategies file with 3 main strategies for geolog so far: native,…
mauricioschneider Jul 1, 2015
49a2558
Implement fallback in case native geoloc fails
mauricioschneider Jul 2, 2015
f57f303
Fix google geoloc provider specs.
mauricioschneider Jul 2, 2015
1f30770
Fix spec for codeship timeout on remote request.
mauricioschneider Jul 2, 2015
9adf305
Use fixture for AP list in geoloc spec.
mauricioschneider Jul 3, 2015
f198603
Use semver wrapper to check if os x version >= 10.6 in geo provider.
mauricioschneider Jul 3, 2015
c18ba91
Update windows.devices.geolocation library
mauricioschneider Jul 9, 2015
25b7459
Add specs for geoip
mauricioschneider Jul 10, 2015
b679524
Fix google ip geoloc spec to use stub in all request but the last one.
mauricioschneider Jul 10, 2015
8d0c8cc
WIP: Geonative strategy specs.
mauricioschneider Jul 13, 2015
8cd548b
Fix spec for geonative strategy
mauricioschneider Jul 14, 2015
b9bd1e0
Add specs for errors on darwin geoloc strategy.
mauricioschneider Jul 14, 2015
701d9b9
Add stub for os_version in case specs are ran in OS other than OS X.
mauricioschneider Jul 14, 2015
22fc407
Remove unnecessary whitespace
mauricioschneider Jul 14, 2015
b8f14ca
Remove extra vertical whitespace for consistency
mauricioschneider Jul 14, 2015
a192d17
Add specs to google geoloc for when access_point_list returns error.
mauricioschneider Jul 14, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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