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
JavaScript
;
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;