lgaticaq/tracking-parser

View on GitHub
src/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
A
96%
'use strict'

const mobileLocator = require('mobile-locator')
const meitrack = require('meitrack-parser')
const cellocator = require('cellocator-parser')
const queclink = require('queclink-parser')
const rg = require('simple-reverse-geocoder')
const tz = require('tz-parser')

/**
 * Set client Redis for cache results
 * @param  {String} uri Redis connection string. Ex redis://user:pass@host:port/db
 * @return {Void}
 */
const setCache = uri => {
  rg.setCache(uri)
}

/**
 * Get imei from raw data
 * @param  {Buffer} raw Raw data from tracking device
 * @return {String}     Imei
 */
const getImei = raw => {
  const fns = [
    tz.getImei,
    meitrack.getImei,
    cellocator.getImei,
    queclink.getImei
  ]
  const imei = fns.map(x => x(raw)).find(x => x !== null) || null
  return imei
}

/**
 * Set geolocation from cell tower information
 * @param  {Object}          data        Parsed data
 * @param  {Number}          options.mcc Mobile country code
 * @param  {Number}          options.mnc Mobile network code
 * @param  {String}          options.key Google api key
 * @return {Promise<Object>}             Parsed data with geolocation
 */
const setLoc = (data, { mcc = 730, mnc = 1, key = null } = {}) => {
  const locate = mobileLocator('google', { key })
  return locate({ mcc, mnc, lac: data.lac, cid: data.cid })
    .then(coords => {
      data.loc = {
        type: 'Point',
        coordinates: [coords.longitude, coords.latitude]
      }
      data.gps = 'triangulation'
      return data
    })
    .catch(() => data)
}

/**
 * Set address
 * @param  {Object}         data   Parsed data
 * @param  {String}         apiKey Google api key
 * @return {Promise<Object>}        Parsed data with address
 */
const setAddress = (data, apiKey = null) => {
  if (!data.loc) return Promise.resolve(data)
  return rg
    .getAddress(data.loc, apiKey)
    .then(address => {
      data.address = address
      return data
    })
    .catch(() => {
      return data
    })
}

/**
 * Set gps (enable, triangulation, disable) and set address
 * @param  {Object}          data           Parsed data
 * @param  {Number}          options.mcc    Mobile country code
 * @param  {Number}          options.mnc    Mobile network code
 * @param  {String}          options.apiKey Google api key
 * @return {Promise<Object>}                Parsed data with geolocation and address
 */
const setGps = (data, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  if (data.type !== 'data') return Promise.resolve(data)
  data.gps = data.loc ? 'enable' : 'disable'
  if (data.gps === 'enable') {
    return setAddress(data, apiKey)
  }
  return setLoc(data, {
    mcc: data.mcc || mcc,
    mnc: data.mnc || mnc,
    key: apiKey
  }).then(data => {
    return setAddress(data, apiKey)
  })
}

/**
 * Parse raw data
 * @param  {Buffer}               raw Raw data
 * @return {Object|Array<Object>}     Parsed data
 */
const simpleParse = raw => {
  const fns = [tz.parse, meitrack.parse, cellocator.parse, queclink.parse]
  const data = fns.map(x => x(raw)).find(x => x.type !== 'UNKNOWN') || {
    raw: raw.toString(),
    type: 'UNKNOWN'
  }
  return data
}

/**
 * Parse raw data, set, geolocation, gps (enable, triangulation, disable) and address
 * @param  {Buffer}                        raw            Raw data
 * @param  {Number}                        options.mcc    Mobile country code
 * @param  {Number}                        options.mnc    Mobile network code
 * @param  {String}                        options.apiKey Google api key
 * @return {Promise<Object|Array<Object>>}                Parsed data with geolocation, gps and address
 */
const parse = (raw, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  try {
    const options = { mcc, mnc, apiKey }
    const fns = [tz.parse, meitrack.parse, cellocator.parse, queclink.parse]
    const data = fns.map(x => x(raw)).find(x => x.type !== 'UNKNOWN') || {
      raw: raw.toString(),
      type: 'UNKNOWN'
    }
    if (Array.isArray(data)) {
      return Promise.all(data.map(x => setGps(x, options)))
    } else {
      return setGps(data, options)
    }
  } catch (err) {
    return Promise.reject(err)
  }
}

/**
 * Parse command to raw command
 * @param  {Object} data Command data
 * @return {String}      Raw command
 */
const parseCommand = data => {
  let command = null
  const fns = {
    tz: tz.parseCommand,
    meitrack: meitrack.parseCommand,
    cellocator: cellocator.parseCommand,
    queclink: queclink.parseCommand
  }
  if (fns.hasOwnProperty(data.device)) {
    command = fns[data.device](data)
  }
  return command
}

/**
 * Get raw roboot command
 * @param  {Object} data Command data
 * @return {String}      Raw command
 */
const getRebootCommand = data => {
  let command = null
  if (data.device === 'tz') {
    command = tz.getRebootCommand(data.password || '000000')
  } else if (data.device === 'meitrack') {
    command = meitrack.getRebootCommand(data.imei)
  } else if (data.device === 'queclink') {
    command = queclink.getRebootCommand(data.password, data.serial)
  }
  return command
}

/**
 * Get ack command
 * @param  {Object} data manufacturer, counter and others
 * @return {Object}      Raw command ack and limit
 */
const getAck = data => {
  let ack
  let limit
  if (data.manufacturer === 'queclink') {
    ack = Buffer.from(
      queclink.getAckHeartBeat(data.protocolVersion, data.counter)
    )
    limit = queclink.limitAck || 65535
  } else if (data.manufacturer === 'cellocator') {
    ack = cellocator.ack(data.unitId, data.counter, data.serialId)
    limit = cellocator.limitAck || 255
  }
  return { ack, limit }
}

/**
 * Check if raw data is a HeartBeat
 * @param  {Object} data manufacturer and raw data
 * @return {Boolean}
 */
const isHeartBeat = data => {
  if (data.manufacturer === 'queclink') {
    return queclink.isHeartBeat(data.raw)
  }
  return false
}

module.exports = {
  getImei: getImei,
  setCache: setCache,
  simpleParse: simpleParse,
  parse: parse,
  parseCommand: parseCommand,
  getRebootCommand: getRebootCommand,
  getCellocatorAck: cellocator.ack,
  getAck: getAck,
  isHeartBeat: isHeartBeat
}