UNPKG

geosearch

Version:

Geo location service using multiple free independent services and modules

408 lines (362 loc) 12.4 kB
"use strict"; var path = require('path'); var conf = require('./configs'); var helper = require('./helpers'); var async = require('async'); var util = require('util'); var net = require('net'); var setFields = conf.getVal('geoObject'); var setServices = conf.getVal('services'); var setCommonOptions = conf.getVal('commonOptions'); var debug = require('debug')(conf.getVal('projectName') + ":app"); var GeoError = require('./helpers/geo-error'); /** * * @constructor */ function GeoSearcher() { //this.options; //this.optionsError; // //this.commonOptions; //this.services; } /** * Initialization * @param options */ GeoSearcher.init = function (options) { this.options = options; this.optionsError = null; this.files = {}; this.commonOptions = JSON.parse(JSON.stringify(setCommonOptions)); this.services = JSON.parse(JSON.stringify(setServices)); var self = this; function findLocalFiles() { for (var serv in self.services) { if (!self.services.hasOwnProperty(serv)) { continue; } if (self.services[serv]['type'] === 'local') { self.files[serv] = {}; if (typeof self.services[serv]['dbfile'] === 'object') { for (var file in self.services[serv]['dbfile']) { self.files[serv][self.services[serv]['dbfile'][file]] = self.services[serv][self.services[serv]['dbfile'][file]]; } } else { self.files[serv]['dbfile'] = self.services[serv]['dbfile']; } } } } findLocalFiles(); }; GeoSearcher.filterIpv6 = function (ip) { if (!net.isIPv6(ip)) { return; } var self = this; for (var serviceId in self.services) { if (!self.services.hasOwnProperty(serviceId)) { continue; } var ipv6Enabled = conf.getVal('services:' + serviceId + ":v6"); if (!ipv6Enabled) { delete self.services[serviceId]; } } }; /** * * @param {Object} options * @return {GeoSearcher} */ GeoSearcher.setOptions = function (options) { this.resetOptions(); this.init(options); if (options !== undefined) { if (options.services !== undefined && Object.keys(options.services).length) { for (var iter in options.services) { if (this.services[iter] !== undefined) { this.services[iter]['active'] = options.services[iter]; } } } if (options.files !== undefined && Object.keys(options.files).length) { for (var servName in options.files) { if (this.files[servName] !== undefined) { this.files[servName] = options.files[servName]; } } } if (options.common !== undefined && Object.keys(options.common).length) { for (var it in options.common) { this.commonOptions[it] = options.common[it]; } if (options.common.checkField !== undefined) { if (Object.keys(setFields).indexOf(options.common.checkField) === -1) { this.optionsError = 'Rechecking field in common option is invalid(' + options.common.checkField + ').\n Must be one of these: ' + Object.keys(setFields).join(','); //throw new Error('Rechecking field in common option is invalid(' + options.common.checkField + '). Must be one of these: ' + Object.keys(setFields).join(',')); } } if (options.common.checkLevel !== undefined) { if (parseInt(options.common.checkLevel) < 1 || isNaN(options.common.checkLevel)) { this.commonOptions.checkLevel = 1; } } } } return this; }; /** * * @return {GeoSearcher} */ GeoSearcher.resetOptions = function () { this.init([]); return this; }; /** * * @param ip * @param callback */ GeoSearcher.lookup = function (ip, callback) { debug(ip + " start"); if (this.optionsError !== null && this.optionsError !== undefined) { callback(new GeoError(this.optionsError), null); } if (this.options === undefined) { this.init({}); } var self = this; var options = this.options; var accumulatedResult = [], isDone = false; var sorted; var setupFields = JSON.parse(JSON.stringify(setFields)); /** * * @return {boolean} */ function hasFoundRequested() { var data = handleAccumulated(); for (var it in setupFields) { if (setupFields[it] === true && data[it] === null) { return false; } } //If commonOptions.checkLevel>1 //commonOptions.checkField value should be the same in {{commonOptions.checkLevel}} number of services //e.g commonOptions.checkLevel:2,commonOptions.checkField:countryCode -> //then ipinfo countryCode=US && ip-api countryCode=US -> //then returning true; if (self.commonOptions.checkLevel > 1) { var checkCounter = []; var fieldValues = {}; for (var serv in accumulatedResult) { for (var dataField in accumulatedResult[serv]) { if (dataField === self.commonOptions.checkField) { checkCounter.push(accumulatedResult[serv][dataField]); if (fieldValues[accumulatedResult[serv][dataField]] === undefined) { fieldValues[accumulatedResult[serv][dataField]] = 1; } else { fieldValues[accumulatedResult[serv][dataField]]++; } } if (fieldValues[accumulatedResult[serv][dataField]] === self.commonOptions.checkLevel) { return true; } } } return Object.keys(accumulatedResult).length === Object.keys(self.services).length ? true : false; } return true; } /** * * @return {{}} */ function handleAccumulated() { var result = {}; var keys = Object.keys(accumulatedResult[0]); for (var iter in keys) { if (!keys.hasOwnProperty(iter)) { continue; } result[keys[iter]] = null; } var methods = []; var requestTime = 0; for (var iterFirst in accumulatedResult) { if (!accumulatedResult.hasOwnProperty(iterFirst)) { continue; } methods[accumulatedResult[iterFirst].method] = accumulatedResult[iterFirst].requestTime; requestTime += accumulatedResult[iterFirst].requestTime; } for (var i in keys) { if (!keys.hasOwnProperty(i)) { continue; } for (var it in accumulatedResult) { if (!accumulatedResult.hasOwnProperty(it)) { continue; } if (accumulatedResult[it][keys[i]] !== null) { result[keys[i]] = accumulatedResult[it][keys[i]]; } if (keys[i] === 'requestTime' && accumulatedResult[it][keys[i]]) { result[keys[i]] += accumulatedResult[it][keys[i]]; } } } result.requestTime = requestTime; result.usedMethods = methods; return result; } /** * * @param fields */ function setSearchedFields(fields) { for (var iter in fields) { if (!fields.hasOwnProperty(iter)) { continue; } if (setupFields.hasOwnProperty(iter)) { setupFields[iter] = fields[iter]; } } } if (net.isIP(ip) === 0) { callback(new GeoError(ip + " is not IP"), null); return; } self.filterIpv6(ip); if (options && Object.keys(options).length) { options.fields = options.fields || {}; if (options.fields.length) { //preventing service fields to be updated outside of this particular ip-data search //var setupFields = util._extend({}, setupFields); } setSearchedFields(options.fields); if (options.services !== undefined && options.services.length) { //preventing services status to be updated outside of this particular ip-data search //var services = util._extend({}, services); } //options.services = options.services || {}; //setSearchServices(options.services); } else { setSearchedFields({}); } this.cleanServices(); if (!Object.keys(this.services).length) { callback(new GeoError("No methods are allowed"), null); } //var cbStack = 0; sorted = helper.sort(this.services, true, 'priority', 'asc'); var queue = async.priorityQueue(function (task, callback) { debug("run ", task.title); callback(task.ip, task.callback); }, 3); var tries = 0; var cbStack = Object.keys(sorted).length; for (var service in sorted) { if (!sorted.hasOwnProperty(service)) { continue; } //++cbStack; var servFile = path.join(__dirname, "services/" + sorted[service][0]); var fn = require(servFile); //set local paths if necessary self.setPath(fn, sorted[service][0]); var cb = function (err, result) { tries++; debug('finished and killed; ' + queue.running(), queue.started); cbStack--; if (!err) { debug(result.method + " found"); if (cbStack < 0) { } accumulatedResult.push(result); if (hasFoundRequested() && !isDone) { isDone = true; debug('is idle; ' + queue.idle()); var res = handleAccumulated(); //queue.kill(); //queue.tasks = []; callback(null, res); //return; } else { debug('not completed'); } } else { debug("err", err); } if (cbStack <= 0 && !isDone) { debug('nofound'); //all services have already run but not all required fields found -> callback(new GeoError("Geo data was not found"), null); //queue.kill(); //queue.tasks = []; } }; queue.push({ title: sorted[service][0], ip: ip, callback: cb }, sorted[service][1].priority, fn.lookup); } queue.drain = function () { if (accumulatedResult && isDone) { queue.kill(); queue.tasks = []; } }; queue.empty = function () { debug('all passed'); } }; /** * * @return {GeoSearcher} */ GeoSearcher.cleanServices = function () { for (var iter in this.services) { if (!this.services.hasOwnProperty(iter)) { continue; } if (this.services[iter].active != true) { delete this.services[iter]; } } return this; }; GeoSearcher.setPath = function (service, servName) { if (!this.options) { return this; } if (!this.options.files) { return this; } if (!this.options.files[servName]) { return this; } if (conf.getVal('services:' + servName + ":type") !== 'local') { return this; } var params = conf.getVal('services:' + servName + ":dbfile"); if (typeof params === 'object') { for (var iter in params) { if (!params.hasOwnProperty(iter)) { continue; } if (this.options.files[servName][params[iter]]) { service.setParam(params[iter], this.options.files[servName][params[iter]]); } } } else { service.setParam("dbfile", this.options.files[servName]); } }; module.exports = GeoSearcher;