var Options = { // stage1 locate : { primary : {http:'ag-0.de:9999',https:'ag-0.de:9998',path:'/json',timeout:1000}, secondary : {http:'ip-api.com:80',https:'ip-api.com:80',path:'/json',timeout:2000}, }, // stage2 locate5 : { // secondary : {https:'api.beacondb.net:443',path:'/v1/geolocate?key=test'}, }, // stage 3: lat,lon -> location info lookup reverse : { primary : {https:'api.opencagedata.com',path:'/geocode/v1/json?q=LAT+LON&key=f4280d4685bd4241b2f754957b86dac6'}, secondary: {https:'nominatim.openstreetmap.org',path:'/reverse/?format=json&lat=LAT&lon=LON'}, }, verbose : 0 } var http=Require("http"),https; /* https on demand */ function ip(url) { return url.split(':')[0] } function port(url) { return url.split(':')[1] } // 1. GPS/GEO via IP and external database service function stage1 (cb,options,location) { var e,r; if (!http.xhr && http.request) { // node.js e={hostname:ip(options.http), path:options.path||'',method:"GET", port:port(options.http)}; if (Options.verbose) console.log('locate.stage1',e); r=http.request(e,function(a){ a.setEncoding("utf8"); var c=""; a.on("data",function(a){c+=a}), a.on("end",function(){ try { var info=JSON.parse(c); if (Options.verbose) console.log('locate.stage1.res',info); Object.assign(location,{ ip:info.query, gps:{lat:info.lat,lon:info.lon}, geo:{city:info.city,country:info.country,countryCode:info.countryCode,region:info.region,zip:info.zip} }) return cb(location); } catch (err) { return cb(err); } }) }); r.on("error",function(a) { if (Options.verbose) console.log('locate.stage1.err',a); return cb(a); }) r.setTimeout(options.timeout,function() { if (Options.verbose) console.log('locate.stage1.err',"ETIMEOUT"); return cb(new Error("ETIMEOUT")); }) r.end(); } else { // Browser var proto=document.URL.indexOf('https')==0?'https':'http'; e={uri:proto+'://'+options[proto]+'/'+options.path, method:"GET", headers:{}}; if (Options.verbose) console.log('stage1',e); http.request(e,function(err,xhr,body){ if (err) { if (Options.verbose) console.log('locate.stage1.err',err); return cb(err); } try { var info=JSON.parse(body); if (Options.verbose) console.log('locate.stage1.res',info); Object.assign(location,{ ip:info.query, gps:{lat:info.lat,lon:info.lon}, geo:{city:info.city,country:info.country,countryCode:info.countryCode,region:info.region,zip:info.zip} }) return cb(location); } catch (err) { if (Options.verbose) console.log('locate.stage1.err',err); return cb(err); } }) } } // 2. GPS via IP and external database service function stage2 (cb,options,location) { if (!https || !https.request) return cb(new Error('ENETWORK')); if (!https.xhr && https.request) { var e = { hostname: ip(options.https), port: port(options.https), path: options.path, method: 'GET' } if (Options.verbose) console.log('locate.stage2',e); var req = https.request(e, function (res) { res.on('data', function (d){ try { var pos = JSON.parse(d); if (Options.verbose) console.log('locate.stage3.res',pos); location.gps5 = { lat: pos.location.lat, lon:pos.location.lng } cb(location) } catch (e) { if (Options.verbose) console.log('locate.stage2.err',e); cb(e) }; }); }) req.on('error', function (e) { cb(e); }); req.end(); } else { // Browser e={uri:'https://'+options.https+'/'+options.path, method:"GET", headers:{}}; if (Options.verbose) console.log('locate.stage2',e); https.request(e,function(err,xhr,body){ if (err) { if (Options.verbose) console.log('locate.stage2.err',err); return cb(err); } try { var pos = JSON.parse(body); if (Options.verbose) console.log('locate.stage2.res',pos); location.gps5 = { lat: pos.location.lat, lon:pos.location.lng } return cb(location); } catch (err) { if (Options.verbose) console.log('locate.stage2.err',err); return cb(err); } }) } } // GPS -> GEO mapping by external database service, more precise information // Should use only gps5 (based on GPS), not gps (based in IP address) function stage3 (cb,options,location) { if (!https || !https.request) return cb(new Error('ENETWORK')); // location.gps is @IP location var gps = location.gps5 // || location.gps if (!gps) return cb(new Error("No GPS location")); options.path=options.path .replace(/LAT/,gps.lat) .replace(/LON/,gps.lon); if (!https.xhr && https.request) { var e = { hostname: ip(options.https), port: port(options.https), path: options.path, method: 'GET' } if (Options.verbose) console.log('locate.stage3',e); var req = https.request(e, function (res) { res.on('data', function (d){ try { var res = JSON.parse(d); var loc; if (Options.verbose) console.log('locate.stage3.res',res); if (res.address) loc=res.address; else if (res.results && res.results[0]) loc=res.results[0].components; location.geo5 = { city:loc.city, zip:loc.postcode, street:loc.road, number:loc.house_number, country:loc.country } cb(location) } catch (e) { if (Options.verbose) console.log('locate.stage3.err',e); cb(e) }; }); }) req.on('error', function (e) { if (Options.verbose) console.log('locate.stage3.err',e); cb(e); }); req.end(); } else { // Browser e={uri:'https://'+options.https+'/'+options.path, method:"GET", headers:{}}; if (Options.verbose) console.log('locate.stage3',e); https.request(e,function(err,xhr,body){ if (err) { if (Options.verbose) console.log('locate.stage3.err',err); return cb(err); } try { var res = JSON.parse(body); var loc; if (Options.verbose) console.log('locate.stage3.res',res); if (res.address) loc=res.address; else if (res.results && res.results[0]) loc=res.results[0].components; location.geo5 = { city:loc.city, zip:loc.postcode, street:loc.road, number:loc.house_number, country:loc.country } return cb(location); } catch (err) { if (Options.verbose) console.log('locate.stage3.err',err); return cb(err); } }) } } var todo = { // 1. Direct ISP - IP - GPS/GEO lookup // 1a. with proxy stage1A: function (cb,errors,location) { stage1(function (res) { if (res instanceof Error) { errors.push({url:Options.locate.primary,error:res}); todo.stage1B(cb,errors,location); } else { todo.stage2A(cb,errors,location); } },Options.locate.primary,location); }, // 1b. w/o proxy stage1B: function (cb,errors,location) { stage1(function (res) { if (res instanceof Error) { errors.push({url:Options.locate.secondary,error:res}); todo.stage2A(cb,errors,location); } else { todo.stage2A(cb,errors,location); } },Options.locate.secondary,location); }, // 2. Get geo position (lat,lon) stage2A: function (cb,errors,location) { if (Options.locate5.secondary) stage2(function (res) { if (res instanceof Error) { errors.push({url:Options.locate5.secondary,error:res}); todo.finalize(cb,errors,location); } else { todo.stage2B(cb,errors,location); } },Options.locate5.secondary,location); else todo.stage2B(cb,errors,location); }, // 3. Get geo location (country,..) stage2B: function (cb,errors,location) { stage3(function (res) { if (res instanceof Error) { errors.push({url:Options.reverse.primary,x:1,error:res}); todo.finalize(cb,errors,location); } else { todo.finalize(cb,errors,location); } },Options.reverse.primary,location); }, finalize : function (cb,errors,location) { cb(location,errors); } } function locate (cb,options) { var e; if (options) Options=Object.assign(Options,options); if (typeof require == 'function' && !https) try { https = require('https'); } catch (e) { /* TODO Browser */ } else if (http.xhr) https = http; if (!http) return cb(new Error('ENOTSUPPORTED')); todo.stage1A(cb,[],{}); return; } module.exports={locate:locate,options:Options};