UNPKG

echolocate

Version:

Get an Echo device's physical location and timezone using the Alexa API and Geo-location.

346 lines (245 loc) 7.5 kB
'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var config = _interopDefault(require('config')); var bluebird = require('bluebird'); var dynasty = _interopDefault(require('dynasty')); var Geocoder = _interopDefault(require('node-geocoder')); var timezoner = _interopDefault(require('timezoner')); var axios = _interopDefault(require('axios')); var babelHelpers = {}; var asyncGenerator = function () { function AwaitValue(value) { this.value = value; } function AsyncGenerator(gen) { var front, back; function send(key, arg) { return new Promise(function (resolve, reject) { var request = { key: key, arg: arg, resolve: resolve, reject: reject, next: null }; if (back) { back = back.next = request; } else { front = back = request; resume(key, arg); } }); } function resume(key, arg) { try { var result = gen[key](arg); var value = result.value; if (value instanceof AwaitValue) { Promise.resolve(value.value).then(function (arg) { resume("next", arg); }, function (arg) { resume("throw", arg); }); } else { settle(result.done ? "return" : "normal", result.value); } } catch (err) { settle("throw", err); } } function settle(type, value) { switch (type) { case "return": front.resolve({ value: value, done: true }); break; case "throw": front.reject(value); break; default: front.resolve({ value: value, done: false }); break; } front = front.next; if (front) { resume(front.key, front.arg); } else { back = null; } } this._invoke = send; if (typeof gen.return !== "function") { this.return = undefined; } } if (typeof Symbol === "function" && Symbol.asyncIterator) { AsyncGenerator.prototype[Symbol.asyncIterator] = function () { return this; }; } AsyncGenerator.prototype.next = function (arg) { return this._invoke("next", arg); }; AsyncGenerator.prototype.throw = function (arg) { return this._invoke("throw", arg); }; AsyncGenerator.prototype.return = function (arg) { return this._invoke("return", arg); }; return { wrap: function (fn) { return function () { return new AsyncGenerator(fn.apply(this, arguments)); }; }, await: function (value) { return new AwaitValue(value); } }; }(); var asyncToGenerator = function (fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }; babelHelpers; const PERMISSIONS = { FULL_ADDRESS: 'address', COUNTRY_POSTAL: 'countryAndPostalCode' }; const ADDRESS_API = '{apiEndpoint}/v1/devices/{deviceId}/settings/address/{perm}'; var getDeviceAddress = (() => { var _ref = asyncToGenerator(function* (deviceId, { apiEndpoint = 'https://api.amazonalexa.com', consentToken, permissions = PERMISSIONS.COUNTRY_POSTAL }) { const url = ADDRESS_API.replace(/{apiEndpoint}/, apiEndpoint).replace(/{deviceId}/, deviceId).replace(/{perm}/, permissions === PERMISSIONS.COUNTRY_POSTAL ? PERMISSIONS.COUNTRY_POSTAL : ''); const { data } = yield axios.get(url, { headers: { Authorization: `Bearer ${consentToken}` } }); return data; }); function getDeviceAddress(_x, _x2) { return _ref.apply(this, arguments); } return getDeviceAddress; })(); /** * Uses the device's id and a consentToken to perform geo-lookups and build up a location object * for where the device is located. * * @param {String} deviceId * @param {DeviceLocationQueryOptions} options * @returns {DeviceLocation} */ let buildDeviceLocation = (() => { var _ref2 = asyncToGenerator(function* (deviceId, options) { const address = yield getDeviceAddress(deviceId, options); const [geo] = yield geocoder.geocode(address.postalCode); const timezone = yield bluebird.promisify(timezoner.getTimeZone)(geo.latitude, geo.longitude); return Object.assign({ deviceId, latitude: geo.latitude, longitude: geo.longitude, formattedAddress: geo.formattedAddress, timezone }, address); }); return function buildDeviceLocation(_x3, _x4) { return _ref2.apply(this, arguments); }; })(); let set = (() => { var _ref3 = asyncToGenerator(function* (device) { if (config.has('echolcate.dbTableName')) { yield devices.insert(device); } return device; }); return function set(_x5) { return _ref3.apply(this, arguments); }; })(); let get = (() => { var _ref4 = asyncToGenerator(function* (deviceId) { if (config.has('echolcate.dbTableName')) { return yield devices.find(deviceId); } return null; }); return function get(_x6) { return _ref4.apply(this, arguments); }; })(); /** * @typedef {Object} DeviceLocationQueryOptions * @property {Object} apiEndpoint -- API endpoint to get the location from. This will be provided * in the Alexa Skill request object. Defaults to https://api.amazonalexa.com * @property {String} consentToken -- consentToken from the ASK request session * @property {Boolean} [skipDB] -- Whether it should skip looking for the deviceId in the database */ /** * @typedef {Object} DeviceLocation * @property {String} deviceId * @property {String} countryCode * @property {String} postalCode * @property {String} city * @property {String} country * @property {String} formattedAddress * @property {String} latitude * @property {String} longitude * @property {GMapsTimeZone} timezone */ /** * @typedef {Object} GMapsTimeZone * @property {Number} dstOffset * @property {Number} rawOffset * @property {String} status * @property {String} timeZoneId * @property {String} timeZoneName */ const geocoder = Geocoder({ provider: 'google' }); const dynoClient = dynasty(config.get('echolocate.awsConfig')); const devices = dynoClient.table(config.get('echolocate.dbTableName')); /** * Get an Echo device's physical location and timezone using the Alexa API and Geo-location. * * @param {String} deviceId -- deviceId of the requesting device * @param {DeviceLocationQueryOptions} options * @returns {Promise<Object>} */ var index = (() => { var _ref = asyncToGenerator(function* (deviceId, options) { return !options.skipDB && (yield get(deviceId)) || (yield set(buildDeviceLocation(deviceId, options))); }); function getDeviceLocation(_x, _x2) { return _ref.apply(this, arguments); } return getDeviceLocation; })(); module.exports = index;