ttn-osem-integration Microservice for openSenseMap that provides a
+direct integration with TheThingsNetwork
+to allow straightforward measurement upload from LoRa-WAN devices.
It decodes measurements from an uplink payload from the TTN HTTP Integrations API
for a configured senseBox, and adds the decoded measurements to the database.
There are multiple decoding options provided via profiles, which may be
easily extended to support other sensor configurations or value transformations.
-configuring a box To associate a device on the TTN network with a box on the openSenseMap, there is some configuration required on the openSenseMap. The box has to contain a field box.integrations.ttn with the following structure:
+configuring a box To associate a device on TTN with a box on the openSenseMap, there is some
+configuration required on the openSenseMap. The box has to contain a field
+box.integrations.ttn with the following structure:
ttn: {
// the app_id & dev_id you recieved when registering on TTN
app_id: 'abcd',
@@ -63,7 +66,7 @@ configuring a box To associate a device on the TTN network with a box
}
decoding profiles sensebox/homeDecodes messages which contain 5 measurements of all sensors of the senseBox:home.
The correct sensorIds are matched via their titles. Decoding fits the dragino senseBox:home sketch .
lora-serializationAllows decoding of messages that were encoded with the lora-serialization library .
-The sub-profiles temperature, humidity, uint8, uint16 and unixtime are supported.
+The decoders temperature, humidity, uint8, uint16, unixtime and latLng are supported.
Each encoded value is matched to a sensor via it's _id, sensorType, unit, or title properties.
There may be one or more property defined for each value via sensor_id, sensor_title, sensor_type, sensor_unit.
If one property matches a sensor, the other properties are discarded.
@@ -71,12 +74,29 @@ lora-serializationAllows decoding of messages that were
"ttn": {
"profile": "lora-serialization",
"decodeOptions": [
- { "sensor_unit": "°C", "decoder": "temperature" },
- { "sensor_id": "588876b67dd004f79259bd8b", "decoder": "humidity" },
- { "sensor_type": "TSL45315", "sensor_title": "Beleuchtungsstärke", "decoder": "uint16" }
+ { "decoder": "temperature", "sensor_unit": "°C" },
+ { "decoder": "humidity", "sensor_id": "588876b67dd004f79259bd8b" },
+ { "decoder": "uint16", "sensor_type": "TSL45315", "sensor_title": "Beleuchtungsstärke" }
]
-}When decodeOptions contains an element { "decoder": "unixtime" }, the value will be used as timestamp for all other measurements.
-debugSimple decoder, which decodes a given number of bytes to integer values.
+}
special decoders unixtime & latLng These decoders do not generate a measurement for a sensor of the box, but will
+be used as timestamp or location for the measurements.
+The unixtime and latLng decoders must be defined before the measurements,
+and are applied to all following measurements, until the next latLng or
+unixtime decoder is specified.
+This means that it is possible to send measurements of several timestamps at once:
+"ttn": {
+ "profile": "lora-serialization",
+ "decodeOptions": [
+ // first measurement, will have time of transmission as timestamp
+ { "decoder": "temperature", "sensor_unit": "°C" },
+ // 2nd measurement for same sensor, will have custom timestamp
+ { "decoder": "unixtime" }, // no sensor properties required for special decoders
+ { "decoder": "temperature", "sensor_unit": "°C" },
+ // 3rd measurement, another timestamp
+ { "decoder": "unixtime" },
+ { "decoder": "temperature", "sensor_unit": "°C" }
+ ]
+}debugSimple decoder, which decodes a given number of bytes to integer values.
Requires a config like the following, where the measurements are applied to the sensors in the order of box.sensors.
ttn: {
profile: 'lora-serialization',
@@ -116,13 +136,13 @@ license MIT, see LICENSE
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/lib_database.js.html b/docs/lib_database.js.html
new file mode 100644
index 0000000..7c87336
--- /dev/null
+++ b/docs/lib_database.js.html
@@ -0,0 +1,110 @@
+
+
+
+
+ JSDoc: Source: lib/database.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/database.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * Handles operations on the opensensemap-api database using mongoose.
+ * @module database
+ * @license MIT
+ */
+
+const { Box } = require('openSenseMapAPI').models;
+const { BoxNotFoundError } = require('./errors');
+
+/**
+ * look up a Box in the database for a given ttn configuration
+ * @param {string} app_id
+ * @param {string} dev_id
+ * @param {string} [port]
+ * @returns {Promise<Box>}
+ */
+const boxFromDevId = function boxFromDevId (app_id, dev_id, port) {
+ return Box.find({
+ 'integrations.ttn.app_id': app_id,
+ 'integrations.ttn.dev_id': dev_id
+ })
+ .then(boxes => {
+ if (!boxes.length) {
+ throw new BoxNotFoundError(`for dev_id '${dev_id}' and app_id '${app_id}'`);
+ }
+
+ // filter the boxes by their configured port.
+ // also include boxes with undefined port.
+ const box = boxes.filter(box => {
+ const p = box.integrations.ttn.port;
+
+ return (p === port || p === undefined);
+ })[0];
+
+ if (!box) {
+ throw new BoxNotFoundError(`for port ${port}`);
+ }
+
+ return box;
+ });
+};
+
+/**
+ * look up a Box in the database for a given boxID.
+ * @param {string} id - The ID of the box to retrieve.
+ * @param {Object} [opts={ populate: false, lean: true }] - Query options
+ * @returns {Promise<Box>}
+ */
+const boxFromBoxId = function boxFromBoxId (id, { populate = false, lean = true } = {}) {
+ return Box.findBoxById(id, { populate, lean })
+ .catch(err => Promise.reject(new BoxNotFoundError(err.message)));
+};
+
+module.exports = {
+ boxFromBoxId,
+ boxFromDevId,
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_decoding_debug.js.html b/docs/lib_decoding_debug.js.html
new file mode 100644
index 0000000..6ee6ee1
--- /dev/null
+++ b/docs/lib_decoding_debug.js.html
@@ -0,0 +1,104 @@
+
+
+
+
+ JSDoc: Source: lib/decoding/debug.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/decoding/debug.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * This decoding profile is meant as a general starting point for profiles.
+ * It decodes the buffer according to a custom byteMask, and transforms the
+ * data to integer values.
+ * The byteMask defines the amount of bytes to consume for each measurement.
+ * It is applied to the sensors in the order they are defined.
+ * @module decoding/debug
+ * @license MIT
+ */
+
+const { bytesToInt } = require('./helpers');
+const { DecodingError } = require('../errors');
+
+/**
+ * returns a bufferTransfomer for transformation of a buffer to measurements.
+ * @see module:decoding~bufferToMeasurements
+ * @param {Box} box - The box to retrieve byteMask and sensorIds from
+ * @return {Array} A bufferTransformer for the box
+ * @example <caption>decodeOptions format</caption>
+ * ttn: {
+ * profile: 'debug',
+ * // use first 3 bytes for first sensor, 4th byte for second, next two bytes for third sensor
+ * decodeOptions: [3, 1, 2]
+ * }
+ */
+const createBufferTransformer = function createBufferTransformer (box) {
+ const byteMask = box.integrations.ttn.decodeOptions,
+ transformer = [];
+
+ if (!byteMask) {
+ throw new DecodingError('box requires a valid byteMask', 'debug');
+ }
+
+ if (box.sensors.length < byteMask.length) {
+ throw new DecodingError(`box requires at least ${byteMask.length} sensors`, 'debug');
+ }
+
+ for (let i = 0; i < byteMask.length; i++) {
+ transformer.push({
+ sensorId: box.sensors[i]._id.toString(),
+ bytes: byteMask[i],
+ transformer: bytesToInt
+ });
+ }
+
+ return transformer;
+};
+
+module.exports = {
+ createBufferTransformer
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_decoding_helpers.js.html b/docs/lib_decoding_helpers.js.html
new file mode 100644
index 0000000..e5ebc3d
--- /dev/null
+++ b/docs/lib_decoding_helpers.js.html
@@ -0,0 +1,182 @@
+
+
+
+
+ JSDoc: Source: lib/decoding/helpers.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/decoding/helpers.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * @module decoding/helpers
+ * @license MIT
+ */
+
+/**
+ * Transforms an array of bytes into an integer value.
+ * NOTE: uses little endian; LSB comes first!
+ * @param {Array} bytes - data to transform
+ * @return {Number}
+ */
+const bytesToInt = function bytesToInt (bytes) {
+ let v = 0;
+ for (let i = 0; i < bytes.length; i++) {
+ v = v | Number(bytes[i] << (i * 8));
+ }
+
+ return v;
+};
+
+/**
+ * Matches the _ids of a set of sensors to a given set of properties.
+ * @param {Array} sensors - Set of sensors as specified in Box.sensors
+ * @param {Object} sensorMatchings - defines a set of allowed values for one
+ * or more properties. Once a property matches, the others are ignored.
+ * @return {Object} Key-value pairs for each property of sensorMatchings and
+ * the matched sensorIds. If a match was not found, the key is not included.
+ * @example <caption>sensorMatchings</caption>
+ * {
+ * humidity: {
+ * title: ['rel. luftfeuchte', 'luftfeuchtigkeit', 'humidity'],
+ * type: ['HDC1008']
+ * },
+ * pressure: {
+ * title: ['luftdruck', 'druck', 'pressure', 'air pressure'],
+ * unit: ['°C', '°F']
+ * }
+ * }
+ * @example <caption>result</caption>
+ * {
+ * humidity: '588876b67dd004f79259bd8a',
+ * pressure: '588876b67dd004f79259bd8b'
+ * }
+ */
+const findSensorIds = function findSensorIds (sensors, sensorMatchings) {
+ const sensorMap = {};
+
+ // for each sensorId to find
+ for (const phenomenon in sensorMatchings) {
+
+ // for each sensor variable to look up
+ for (const sensorProp in sensorMatchings[phenomenon]) {
+
+ const aliases = sensorMatchings[phenomenon][sensorProp].map(a => a.toLowerCase());
+ let foundIt = false;
+
+ // for each unmatched sensor
+ for (const sensor of sensors) {
+ if (!sensor[sensorProp]) {
+ continue;
+ }
+
+ const prop = sensor[sensorProp].toString().toLowerCase();
+
+ if (aliases.includes(prop)) {
+ sensorMap[phenomenon] = sensor._id.toString();
+ foundIt = true;
+ break;
+ }
+ }
+
+ // don't check the other properties, if one did match
+ if (foundIt) {
+ break;
+ }
+ }
+ }
+
+ return sensorMap;
+};
+
+/**
+ * Factory that returns a bufferTransformer `onResult`-hook, which applies the
+ * value of the measurement from `sensorId` to the `property` of the following
+ * measurements, until the measurement with this `sensorId` is found.
+ * The used measurement is removed from the result.
+ * If a transformer function is passed, the value will be passed trough the function.
+ * @param {Object} args Parameters as mentioned in description.
+ * @example
+ * {
+ * sensorId: String, // sensorId to match measurement against
+ * property: String, // property to assign the value of the matched measure
+ * transformer: Function // optional value transformation function
+ * }
+ */
+const applyValueFromMeasurement = function applyValueFromMeasurement (args) {
+ const { sensorId, property, transformer } = args;
+
+ return function (measurements) {
+ // find "measurement" from the unixtime decoder
+ // and discard it.
+ for (let k = 0; k < measurements.length; k++) {
+ if (measurements[k].sensor_id === sensorId) {
+ const value = transformer
+ ? transformer(measurements[k].value)
+ : measurements[k].value;
+
+ measurements.splice(k, 1);
+
+ // apply the value to the following measurements,
+ // until the next instance of `sensorId` is found.
+ while (
+ measurements[k] &&
+ measurements[k].sensor_id !== sensorId
+ ) {
+ measurements[k++][property] = value;
+ }
+
+ break;
+ }
+ }
+ };
+};
+
+module.exports = {
+ applyValueFromMeasurement,
+ bytesToInt,
+ findSensorIds
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_decoding_index.js.html b/docs/lib_decoding_index.js.html
new file mode 100644
index 0000000..5505f4d
--- /dev/null
+++ b/docs/lib_decoding_index.js.html
@@ -0,0 +1,243 @@
+
+
+
+
+ JSDoc: Source: lib/decoding/index.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/decoding/index.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * Provides a generic decoding interface into which multiple decoding profiles
+ * may be hooked. Implemented profiles are
+ * {@link module:decoding/lora-serialization|lora-serialization},
+ * {@link module:decoding/sensebox_home|sensebox/home},
+ * {@link module:decoding/debug|debug}
+ * @module decoding
+ * @license MIT
+ */
+
+const { transformAndValidateArray, json } = require('openSenseMapAPI').decoding;
+const { createLogger } = require('../logging');
+const { DecodingError } = require('../errors');
+
+const profiles = {
+ /* eslint-disable global-require */
+ 'debug': require('./debug'),
+ 'lora-serialization': require('./lora-serialization'),
+ 'sensebox/home': require('./sensebox_home')
+};
+
+const log = createLogger('decoder');
+
+/**
+ * Decodes a Buffer to an array of measurements according to a bufferTransformer
+ * @private
+ * @param {Buffer} buffer - the data to decode
+ * @param {Array} bufferTransformer - defines how the data is transformed.
+ * Each element specifies a transformation for one measurement.
+ * @example <caption>Interface of bufferTransformer elements</caption>
+ * {
+ * bytes: Number, // amount of bytes to consume for this measurement
+ * sensorId: String, // corresponding sensor_id for this measurement
+ * transformer: Function // function that accepts an Array of bytes and
+ * // returns the measurement value
+ * onResult: Function // hook that recieves all decoded measurements
+ * // once decoding has finished. may modify the
+ * // measurement array.
+ * }
+ * @return {Array} decoded measurements
+ */
+const bufferToMeasurements = function bufferToMeasurements (buffer, bufferTransformer) {
+ const result = [];
+ let maskLength = 0, currByte = 0;
+
+ // check mask- & buffer-length
+ for (const mask of bufferTransformer) {
+ maskLength = maskLength + mask.bytes;
+ }
+
+ if (maskLength !== buffer.length) {
+ throw new DecodingError(`incorrect amount of bytes: got ${buffer.length}, should be ${maskLength}`);
+ }
+
+ // feed each bufferTransformer element
+ for (const mask of bufferTransformer) {
+ const maskedBytes = buffer.slice(currByte, currByte + mask.bytes);
+
+ result.push({
+ sensor_id: mask.sensorId,
+ value: mask.transformer(maskedBytes)
+ });
+
+ currByte = currByte + mask.bytes;
+ }
+
+ // apply onResult hook from bufferTransformer elements
+ for (const mask of bufferTransformer) {
+ if (typeof mask.onResult === 'function') {
+ mask.onResult(result);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Transforms a buffer to a validated set of measurements according to a boxes
+ * TTN configuration.
+ * @param {Buffer} buffer - The data to be decoded
+ * @param {Box} box - A box from the DB for lookup of TTN config & sensors
+ * @param {String} [timestamp=new Date()] - Timestamp to attach to the measurements.
+ * @return {Promise} Once fulfilled returns a validated array of measurements
+ * (no actual async ops are happening)
+ */
+const decodeBuffer = function decodeBuffer (buffer, box, timestamp) {
+ return Promise.resolve().then(function () {
+ // should never be thrown, as we find a box by it's ttn config
+ if (!buffer.length) {
+ throw new DecodingError('payload may not be empty');
+ }
+
+ if (!box.integrations || !box.integrations.ttn) {
+ throw new DecodingError('box has no TTN configuration');
+ }
+
+ // select bufferTransformer according to profile
+ const profile = profiles[box.integrations.ttn.profile];
+
+ if (!profile) {
+ throw new DecodingError(`profile '${box.integrations.ttn.profile}' is not supported`);
+ }
+
+ const bufferTransformer = profile.createBufferTransformer(box);
+
+ // decode buffer using bufferTransformer
+ const measurements = bufferToMeasurements(buffer, bufferTransformer);
+
+ // if a timestamp is provided, set it for all the measurements
+ if (timestamp) {
+ for (const m of measurements) {
+ m.createdAt = m.createdAt || timestamp;
+ }
+ }
+
+ // validate decoded measurements
+ return transformAndValidateArray(measurements);
+ });
+};
+
+/**
+ * proxy for decodeBuffer, which converts the input data from base64 to a buffer first
+ * @see module:decoding~decodeBuffer
+ * @param {String} base64String
+ * @param {Box} box
+ * @param {String} [timestamp=new Date()]
+ * @return {Promise} Once fulfilled returns a validated array of measurements
+ * (no actual async ops are happening)
+ */
+const decodeBase64 = function decodeBase64 (base64String, box, timestamp) {
+ const buf = Buffer.from(base64String || '', 'base64');
+
+ return decodeBuffer(buf, box, timestamp);
+};
+
+/**
+ * Decodes multiple json encoded measurements and validates them.
+ * Accepts either an measurement array, or an object like
+ * {"sensorId": [value, time, location]}
+ * refer to {@link https://docs.opensensemap.org/#api-Measurements-postNewMeasurements|oSeM docs}
+ * for specific input formats.
+ * @param {Object} data
+ * @return {Promise} Once fulfilled returns a validated array of measurements
+ */
+const decodeJSON = json.decodeMessage;
+
+/**
+ * Decodes the a payload to validated measurements for a given box.
+ * selects the decoder and sensors from `box`, applies `time` if it was not
+ * provided in the payload
+ * @see module:decoding~decodeBuffer
+ * @param {TTNUplink} payload A valid uplink payload object as received from TTN
+ * @param {Box} box The senseBox to match the measurements against
+ * @param {string} [time] An ISODate as fallback for measurement timestamps
+ * @return {Promise} Once fulfilled returns a validated array of measurements
+ */
+const decodeRequest = function decodeRequest (payload, box, time) {
+ // extract time from payload: if available use time from
+ // gateway, else use TTN API time, else use local request time
+ let timeSource = 'local';
+ if (payload.metadata) {
+ time = payload.metadata.time;
+ timeSource = 'TTN api';
+ if (payload.metadata.gateways && payload.metadata.gateways[0].time) {
+ time = payload.metadata.gateways[0].time;
+ timeSource = 'gateway';
+ }
+ }
+
+ if (!box.integrations || !box.integrations.ttn) {
+ throw new DecodingError('box has no TTN configuration');
+ }
+
+ log.trace(`using ${timeSource} time & ${box.integrations.ttn.profile} decoder`);
+
+ if (box.integrations.ttn.profile === 'json') {
+ return payload.payload_fields
+ ? decodeJSON(payload.payload_fields)
+ : Promise.reject(new DecodingError('no payload for profile `json` provided'));
+ }
+
+ return decodeBase64(payload.payload_raw, box, time);
+};
+
+module.exports = {
+ decodeBuffer,
+ decodeBase64,
+ decodeJSON,
+ decodeRequest
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_decoding_lora-serialization.js.html b/docs/lib_decoding_lora-serialization.js.html
new file mode 100644
index 0000000..7ae4243
--- /dev/null
+++ b/docs/lib_decoding_lora-serialization.js.html
@@ -0,0 +1,188 @@
+
+
+
+
+ JSDoc: Source: lib/decoding/lora-serialization.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/decoding/lora-serialization.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * This decoding can decode payloads constructed with {@link https://github.com/thesolarnomad/lora-serialization|lora-serialization}.
+ * @module decoding/lora-serialization
+ * @license MIT
+ */
+
+const loraSerialization = require('lora-serialization').decoder,
+ { createLogger } = require('../logging'),
+ { LoraError } = require('../errors'),
+ { findSensorIds, applyValueFromMeasurement } = require('./helpers');
+
+const log = createLogger('decoder/lora-serialization');
+
+/**
+ * returns a bufferTransfomer for transformation of a buffer to measurements.
+ * The box is required to have a an array of ttn.decodeOptions
+ * @see module:decoding~bufferToMeasurements
+ * @param {Box} box - The box to retrieve decodeOptions and sensorIds from
+ * @return {Array} A bufferTransformer for the box
+ * @example <caption>decodeOptions format</caption>
+ * ttn: {
+ * profile: 'lora-serialization',
+ * decodeOptions: [{
+ * sensor_id: '588876b67dd004f79259bd8a',
+ * decoder: 'temperature' // one of [temperature, humidity, uint8, uint16]
+ * }, {
+ * // sensor_type, sensor_title, sensor_unit is allowed as well
+ * sensor_type: '588876b67dd004f79259bd8b',
+ * decoder: 'VEML6070'
+ * }]
+ * }
+ */
+const createBufferTransformer = function createBufferTransformer (box) {
+ const byteMask = box.integrations.ttn.decodeOptions,
+ bufferTransf = [],
+ sensorMatchings = [];
+
+ if (
+ !byteMask ||
+ byteMask.constructor !== Array ||
+ !byteMask.every(opts => typeof opts === 'object')
+ ) {
+ throw new LoraError('profile \'lora-serialization\' requires valid decodeOptions');
+ }
+
+ let expectedSensorCount = byteMask.length;
+
+ // construct sensorMatchings to find the correct sensorIds
+ for (const el of byteMask) {
+ const match = {};
+ if (el.sensor_id) {
+ match['_id'] = [el.sensor_id];
+ }
+ if (el.sensor_title) {
+ match['title'] = [el.sensor_title];
+ }
+ if (el.sensor_type) {
+ match['sensorType'] = [el.sensor_type];
+ }
+ if (el.sensor_unit) {
+ match['unit'] = [el.sensor_unit];
+ }
+
+ // exception for unixtime & latLng decoder, as its result will not be a
+ // measurement for a sensor but used as timestamp for all measurements
+ if (['unixtime', 'latLng'].includes(el.decoder)) {
+ expectedSensorCount--;
+ } else if (!Object.keys(match).length) {
+ throw new LoraError('invalid decodeOptions. requires at least one of [sensor_id, sensor_title, sensor_type]');
+ } else {
+ sensorMatchings.push(match);
+ }
+ }
+
+ const sensorIds = findSensorIds(box.sensors, sensorMatchings);
+
+ log.trace({
+ sensorMatchings,
+ sensorIds,
+ specialDecoders: byteMask.length - expectedSensorCount
+ }, 'matched sensors');
+
+ if (Object.keys(sensorIds).length !== expectedSensorCount) {
+ throw new LoraError('box does not contain sensors mentioned in byteMask');
+ }
+
+ // create the transformer elements for each measurement.
+ // use a separate counter for sensorIds, b/c unixtime & latLng
+ // decoders have no entry in sensorIds!
+ for (let i = 0, processedSensorIds = 0; i < byteMask.length; i++) {
+ const transformer = loraSerialization[byteMask[i].decoder];
+
+ if (
+ typeof transformer !== 'function' ||
+ byteMask[i].decoder === 'decode'
+ ) {
+ throw new LoraError(`'${byteMask[i].decoder}' is not a supported transformer`);
+ }
+
+ const mask = {
+ transformer,
+ bytes: transformer.BYTES,
+ };
+
+ // if a special decoder is provided, use its value as timestamp or location
+ // for all measurements defined after it via the onResult hook.
+ if (byteMask[i].decoder === 'unixtime') {
+ mask.sensorId = 'MEASURE_TIMESTAMP';
+
+ mask.onResult = applyValueFromMeasurement({
+ sensorId: 'MEASURE_TIMESTAMP',
+ property: 'createdAt',
+ transformer: val => new Date(val * 1000).toISOString()
+ });
+ } else if (byteMask[i].decoder === 'latLng') {
+ mask.sensorId = 'MEASURE_LOCATION';
+
+ mask.onResult = applyValueFromMeasurement({
+ sensorId: 'MEASURE_LOCATION',
+ property: 'location',
+ transformer: val => [val[1], val[0]] // swap lat lng
+ });
+ } else {
+ mask.sensorId = sensorIds[processedSensorIds++];
+ }
+
+ bufferTransf.push(mask);
+ }
+
+ return bufferTransf;
+};
+
+module.exports = {
+ createBufferTransformer
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_decoding_sensebox_home.js.html b/docs/lib_decoding_sensebox_home.js.html
new file mode 100644
index 0000000..17539d8
--- /dev/null
+++ b/docs/lib_decoding_sensebox_home.js.html
@@ -0,0 +1,138 @@
+
+
+
+
+ JSDoc: Source: lib/decoding/sensebox_home.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/decoding/sensebox_home.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * This decoding profile decodes the measurements for the sensors of the
+ * senseBox:home (temperature, humidity, pressure, lightintensity, uvlight).
+ * It applies value transformation fitting for {@link https://github.com/sensebox/random-sketches/blob/master/lora/dragino/dragino.ino|this arduino sketch}.
+ * @module decoding/sensebox_home
+ * @license MIT
+ */
+
+const { bytesToInt, findSensorIds } = require('./helpers');
+const { DecodingError } = require('../errors');
+
+// alternative titles recognized for the sensors
+const sensorMatchings = {
+ temperature: {
+ title: ['temperatur', 'temperature'],
+ },
+ humidity: {
+ title: ['rel. luftfeuchte', 'luftfeuchtigkeit', 'humidity']
+ },
+ pressure: {
+ title: ['luftdruck', 'druck', 'pressure', 'air pressure']
+ },
+ lightintensity: {
+ title: ['licht', 'helligkeit', 'beleuchtungsstärke', 'einstrahlung', 'light', 'light intensity']
+ },
+ uvlight: {
+ title: ['uv', 'uv-a', 'uv-intensität', 'uv-intensity']
+ }
+};
+
+/**
+ * returns a bufferTransfomer for transformation of a buffer to measurements.
+ * @see module:decoding~bufferToMeasurements
+ * @param {Box} box - The box to retrieve sensorIds from
+ * @return {Array} A bufferTransformer for the box
+ */
+const createBufferTransformer = function createBufferTransformer (box) {
+ const sensorMap = findSensorIds(box.sensors, sensorMatchings);
+
+ if (Object.keys(sensorMap).length !== Object.keys(sensorMatchings).length) {
+ throw new DecodingError('box does not contain valid sensors for this profile', 'sensebox/home');
+ }
+
+ const transformer = [
+ {
+ sensorId: sensorMap['temperature'],
+ bytes: 2,
+ transformer: bytes => parseFloat((bytesToInt(bytes) / 771 - 18).toFixed(1))
+ },
+ {
+ sensorId: sensorMap['humidity'],
+ bytes: 2,
+ transformer: bytes => parseFloat((bytesToInt(bytes) / 1e2).toFixed(1))
+ },
+ {
+ sensorId: sensorMap['pressure'],
+ bytes: 2,
+ transformer: bytes => parseFloat((bytesToInt(bytes) / 81.9187 + 300).toFixed(1))
+ },
+ {
+ sensorId: sensorMap['lightintensity'],
+ bytes: 3,
+ transformer (bytes) {
+ const [mod, ...times] = bytes;
+
+ return bytesToInt(times) * 255 + bytesToInt([mod]);
+ }
+ },
+ {
+ sensorId: sensorMap['uvlight'],
+ bytes: 3,
+ transformer (bytes) {
+ const [mod, ...times] = bytes;
+
+ return bytesToInt(times) * 255 + bytesToInt([mod]);
+ }
+ }
+ ];
+
+ return transformer;
+};
+
+module.exports = {
+ createBufferTransformer
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_errors.js.html b/docs/lib_errors.js.html
new file mode 100644
index 0000000..af9ea39
--- /dev/null
+++ b/docs/lib_errors.js.html
@@ -0,0 +1,132 @@
+
+
+
+
+ JSDoc: Source: lib/errors.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/errors.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * All errors handled by the app must inherit from {@link class:TTNError|TTNError}
+ * and should be defined here for easier discovery.
+ * Where applicable, the error subclass should set an HTTP status code
+ * on `this.code`.
+ *
+ * @module errors
+ * @license MIT
+ */
+
+/**
+ * All errors handled by the app must inherit from TTNError
+ * and should be defined here for easier discovery.
+ * @param {any} message
+ */
+class TTNError extends Error { }
+
+/**
+ * Thrown when a request is not authorized.
+ */
+class AuthorizationError extends TTNError {
+ constructor () {
+ super('Not Authorized');
+ this.code = 403;
+ }
+}
+
+/**
+ * Thrown with { code: 404 } when a box was not found in DB.
+ * @param {string} message
+ */
+class BoxNotFoundError extends TTNError {
+ constructor (message) {
+ super(`No box found ${message}`);
+ this.code = 404;
+ }
+}
+
+/**
+ * Thrown with { code: 422 } when the received payload was malformed.
+ * @param {string} message
+ */
+class PayloadError extends TTNError {
+ constructor (message) {
+ super(`Invalid payload: ${message}`);
+ this.code = 422;
+ }
+}
+
+/**
+ * Thrown with { code: 422 } when the received payload content could not be parsed.
+ * @param {string} message
+ * @param {string} decoder The name of the decoder that is erroring
+ */
+class DecodingError extends PayloadError {
+ constructor (message, decoder = 'generic') {
+ super(`${message} (${decoder} decoder)`);
+ this.component = decoder;
+ }
+}
+
+/**
+ * Specialized error for the lora-serialization decoder
+ * @param {string} message
+ */
+class LoraError extends DecodingError {
+ constructor (message) {
+ super(message, 'lora-serialization');
+ }
+}
+
+module.exports = {
+ TTNError,
+ AuthorizationError,
+ BoxNotFoundError,
+ PayloadError,
+ DecodingError,
+ LoraError,
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_logging.js.html b/docs/lib_logging.js.html
new file mode 100644
index 0000000..309d779
--- /dev/null
+++ b/docs/lib_logging.js.html
@@ -0,0 +1,95 @@
+
+
+
+
+ JSDoc: Source: lib/logging.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/logging.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * Configuration for a bunyan logger to be used in the application.
+ * @module logging
+ * @license MIT
+ */
+
+const bunyan = require('bunyan');
+const cfg = require('../config');
+
+const logger = bunyan.createLogger({
+ name: 'ttn-osem-integration',
+ level: cfg.loglevel,
+ serializers: {
+ req: bunyan.stdSerializers.req,
+ err: bunyan.stdSerializers.err,
+ box (box) {
+ return box
+ ? { id: box._id, sensors: box.sensors, ttn: box.integrations.ttn }
+ : {};
+ },
+ res (res) {
+ const { responseTime, result: { code } } = res.locals;
+
+ return { responseTime, code };
+ },
+ }
+});
+
+/**
+ * Creates a bunyan logger that inherits some global settings
+ * @private
+ * @param {string} component name for the logger, stored in the logfield `component`
+ * @param {*} [options] bunyan options to be passed to the logger
+ * @return a bunyan logger
+ */
+const createLogger = function createLogger (component, options) {
+ return logger.child(Object.assign({ component }, options));
+};
+
+module.exports = {
+ createLogger,
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_routes_v1.1.js.html b/docs/lib_routes_v1.1.js.html
new file mode 100644
index 0000000..26d81e4
--- /dev/null
+++ b/docs/lib_routes_v1.1.js.html
@@ -0,0 +1,258 @@
+
+
+
+
+ JSDoc: Source: lib/routes/v1.1.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/routes/v1.1.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * Router for the `/v1.1` prefixed routes, meaning TTN HTTP integrations API v1
+ * and this API v1.
+ * @module routes/v1_1
+ * @license MIT
+ */
+
+const router = require('express').Router(),
+ { boxFromDevId, boxFromBoxId } = require('../database'),
+ { TTNError, AuthorizationError, PayloadError } = require('../errors'),
+ { getOrRegisterDevice } = require('../ttn'),
+ cfg = require('../../config'),
+ decoder = require('../decoding'),
+ { createLogger } = require('../logging');
+
+const log = createLogger('webhook-v1.1');
+
+/**
+ * Express middleware which finishes a request by sending an API response
+ * contained in `res.locals.result`.
+ * Also performs response logging while setting req.locals.responseTime.
+ * Determines status code by checking the error type.
+ *
+ * @private
+ * @param {Object|Error} req.locals.result - `{ code, message }`
+ * @param {Date} req.time - The timestamp when we received the request.
+ * @param {Box} req.box - The matched box
+ */
+const sendResponse = function sendResponse (req, res, next) {
+ const r = res.locals.result;
+ const { message } = r;
+ let { code } = r;
+ if (
+ (r instanceof Error && !(r instanceof TTNError)) || // filter non TTNError.code values
+ !code
+ ) {
+ code = 501;
+ }
+
+ res.status(code).json({ code, message });
+ res.locals.responseTime = Date.now() - req.time.getTime();
+
+ if (r instanceof TTNError) {
+ log.warn({
+ res,
+ box: req.box,
+ payload: req.body,
+ url: req.url
+ }, message);
+ } else if (r.code) {
+ log.info({ res, url: req.url }, message);
+ } else {
+ log.error({
+ err: r,
+ res,
+ box: req.box,
+ payload: req.body,
+ url: req.url,
+ }, message);
+ }
+
+ if (typeof next === 'function') {
+ return next();
+ }
+};
+
+/**
+ * Express middleware which matches a box against the payload, decodes the
+ * message, and stores the measurements for the matched sensors.
+ * Stores the matched box in `req.box`.
+ * @private
+ * @param {string} req.body - The payload in TTN HTTP Integration v1 format.
+ */
+const httpIntegrationHandler = function httpIntegrationHandler (req, res, next) {
+ const { app_id, dev_id, payload_raw, payload_fields, port } = req.body;
+
+ log.debug({ payload: req.body }, 'payload');
+
+ if (
+ !dev_id ||
+ !app_id ||
+ !(payload_raw || payload_fields)
+ ) {
+ res.locals.result = new PayloadError('any of [dev_id, app_id, payload_fields, payload_raw] is missing');
+
+ return next();
+ }
+
+ // look up box for dev_id & app_id in DB
+ boxFromDevId(app_id, dev_id, port)
+ .then(box => {
+ log.debug({ box }, 'matched box');
+ req.box = box;
+
+ // decode measurements from request.body, req.box & req.time
+ return decoder.decodeRequest(req.body, box, req.time.toISOString());
+ })
+
+ // store measurements in DB
+ .then(measurements => {
+ log.debug({ measurements }, 'resulting measurements');
+
+ return req.box.saveMeasurementsArray(measurements);
+ })
+
+ .then(() => {
+ res.locals.result = { code: 201, message: 'measurements created' };
+
+ return next();
+ })
+
+ .catch(err => {
+ res.locals.result = err;
+
+ return next();
+ });
+};
+
+/**
+ * Express middleware that checks the `Authorization` header against
+ * `cfg.authTokens`. Expects a token as defined in `cfg.authTokens`, prefixed
+ * with `"Bearer "`. Rejects a request with status 403 if not authorized.
+ * @private
+ * @param {string} req.header.authorization
+ * @example
+ * curl -H "authorization: Bearer secret" localhost:3000/v1.1/ttndevice/boxID
+ */
+const authOpensensemap = function authOpensensemap (req, res, next) {
+ const token = req.header('authorization');
+ if (token && cfg.authTokens.includes(token.split(' ')[1])) {
+ return next();
+ }
+
+ res.locals.result = new AuthorizationError();
+ sendResponse(req, res);
+};
+
+/**
+ * Express middleware that tries to match a TTN device to the box given in
+ * `req.params.boxId`. If no device was found, a new one is registered.
+ * @private
+ * @param {string} req.params.boxId - The box to match a device `dev_id` against.
+ */
+const registerDeviceHandler = function registerDeviceHandler (req, res, next) {
+ const { boxId } = req.params;
+
+ // make sure the box actually exists
+ return boxFromBoxId(boxId)
+ .then(box => {
+ req.box = box; // for the logger in sendResponse()
+ const dev_eui = box.integrations.ttn ? box.integrations.ttn.dev_eui : '';
+
+ return getOrRegisterDevice(boxId, dev_eui);
+ })
+ .then(device => {
+ res.locals.result = { code: 200, message: device };
+ next();
+ })
+ .catch(err => {
+ res.locals.result = err;
+ next();
+ });
+};
+
+/**
+ * Accepts a POST request from the TTN HTTP integrations uplink API, version 1
+ * as specified {@link https://www.thethingsnetwork.org/docs/applications/http/|here},
+ * and decodes its payload to store a set of measurements for a box.
+ * The box is identified by `app_id`, `dev_id` and `port in `box.integrations.ttn`.
+ * If a box specifies a port, it will only recieve measurements sent on that port.
+ * @name 'POST /v1.1'
+ * @example
+ * curl -X POST -H "content-type: application/json" -d \
+ * '{ "app_id": "asdf", "dev_id": "qwerty", "payload_raw": "kzIrIYzlOycAMgEA" }' \
+ * localhost:3000/v1.1
+ */
+router.post('/', [
+ httpIntegrationHandler,
+ sendResponse,
+]);
+
+/**
+ * Returns the TTN device options for a senseBox. If no TTN device for this box
+ * exists, a new device is registered first.
+ * Requires authentication via a bearer token in the `Authorization` header.
+ *
+ * box <-> device matching:
+ *
+ * app_id = cfg.ttn.appId,
+ * dev_id = box._id,
+ * dev_eui = random,
+ * app_skey/ nwskey = random
+ *
+ * @name 'GET /v1.1/ttndevice/:boxID'
+ * @example
+ * curl -H "authorization: Bearer secretkey" \
+ http://localhost:3000/v1.1/ttndevice/59c585b44fe8d35c5787586e
+ */
+router.get('/ttndevice/:boxId', [
+ authOpensensemap,
+ registerDeviceHandler,
+ sendResponse,
+]);
+
+module.exports = router;
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/lib_ttn.js.html b/docs/lib_ttn.js.html
new file mode 100644
index 0000000..4ca7ad9
--- /dev/null
+++ b/docs/lib_ttn.js.html
@@ -0,0 +1,228 @@
+
+
+
+
+ JSDoc: Source: lib/ttn.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: lib/ttn.js
+
+
+
+
+
+
+
+
+ 'use strict';
+
+/**
+ * Handles communication with the TTN backend, providing functions for
+ * retrieval & registration of devices for a TTN application, and sets up a
+ * MQTT connection, to decode all received uplink messages as measurements.
+ * This assumes that each device in the application maps to a box, where
+ * `dev_id === box._id`.
+ * @module ttn
+ * @license MIT
+ */
+
+const { data, application, key } = require('ttn');
+
+const cfg = require('../config');
+const { boxFromBoxId } = require('./database');
+const { TTNError } = require('./errors');
+const decoder = require('./decoding');
+const { createLogger } = require('./logging');
+
+const log = createLogger('ttn');
+
+/**
+ * Handler for MQTT uplink messages
+ * @private
+ * @param {string} dev_id - the ID of the sending device
+ * @param {Object} message - payload object as described {@link https://www.thethingsnetwork.org/docs/applications/mqtt/api.html|here}
+ */
+const onUplinkMessage = function onUplinkMessage (dev_id, message) {
+ let matchBox;
+
+ // match associated box via dev_id === box._id
+ boxFromBoxId(dev_id, { lean: false })
+ // decode measurements from request.body, req.box & req.time
+ .then(box => {
+ log.debug({ box }, `matched box for dev_id ${dev_id}`);
+ matchBox = box;
+
+ return decoder.decodeRequest(message, box);
+ })
+ // store result in DB
+ .then(measurements => {
+ log.debug({ measurements }, 'resulting measurements');
+
+ return matchBox.saveMeasurementsArray(measurements);
+ })
+ .then(() => log.info(`saved measurements for ${dev_id}`))
+ .catch(err => {
+ if (err instanceof TTNError) {
+ log.warn({ err }, 'could not handle uplink message');
+ } else {
+ log.error({ err });
+ }
+ });
+};
+
+/**
+ * Connects to the TTN MQTT broker and subscribes to incoming messages.
+ * @private
+ * @return {Promise}
+ */
+const initMqtt = function initMqtt () {
+ mqttClient = data(cfg.ttn.appId, cfg.ttn.key)
+ .then(client => {
+ client.on('error', function onTTNError (err) {
+ log.error({ err }, 'could not connect to TTN');
+ });
+
+ client.on('connect', function onTTNConnection () {
+ log.info(`connected to TTN app ${cfg.ttn.appId}`);
+ });
+
+ client.on('uplink', onUplinkMessage);
+
+ return client;
+ });
+
+ return mqttClient;
+};
+
+/**
+ * Resolves the application at any TTN handler.
+ * @private
+ * @return {Promise}
+ */
+const initApp = function initApp () {
+ const { appId, key } = cfg.ttn;
+ appClient = application(appId, key)
+ .then(client => {
+ // to register devices, we need the apps EUI,
+ // which we fetch once from the account server
+ return client.getEUIs()
+ .then(euis => {
+ log.info({ euis }, `resolved TTN app ${appId}`);
+ client.appEui = euis[0];
+
+ return client;
+ });
+ });
+
+ return appClient;
+};
+
+// getters that return a Promise for the clients but initialize them only once
+let mqttClient, appClient;
+
+/**
+ * Initializes and returns a client for the TTN ApplicationManager API for the
+ * configured app_id
+ * @returns {Promise}
+ */
+const getApp = () => appClient || initApp();
+
+/**
+ * Initializes and returns a client for the TTN MQTT Broker for the
+ * configured app_id. Enables the subscription to uplink messages on the
+ * configured application.
+ * @returns {Promise}
+ */
+const getMqtt = () => mqttClient || initMqtt();
+
+/**
+ * Returns a TTN device registered at cfg.ttn.appId. If it does not exist,
+ * a new device is created with randomized keys and boxId === dev_id.
+ * IDEA: maintain local device cache, and check it first?
+ * @param {string} boxId
+ * @param {string} [dev_eui] - The EUI of the device if known
+ * @return {Promise} resolves with the device data
+ */
+const getOrRegisterDevice = function getOrRegisterDevice (boxId, dev_eui) {
+
+ let app;
+
+ return getApp()
+ .then(ttnApp => {
+ app = ttnApp;
+
+ return app.device(boxId);
+ })
+ // the device likely wasn't found, let's create it
+ .catch(err => {
+ if (err.message !== `handler:device:${cfg.ttn.appId}:${boxId} not found`) {
+ throw new Error(`could not find a device for box ${boxId}: ${err.message}`);
+ }
+
+ log.info(`box ${boxId} has no TTN device, registering a new one`);
+
+ // as the enduser has no control over this, we
+ // apply settings that work for most situations
+ const deviceOpts = {
+ // devEui is normally assigned to hardware by lora chip vendor.
+ // if that value is unknown, we generate a pseudo unique ID.
+ // user generated euis must be prefixed with 0x00.
+ devEui: dev_eui || `00${key(7)}`,
+ appEui: app.appEui,
+ disableFCntCheck: true,
+
+ appKey: key(16), // OTAA key
+
+ // ABP keys. OTAA should be used, but we assign keys just in case
+ nwkSKey: key(16),
+ appSKey: key(16),
+ // normally uniquely choosen by network server with prefix 0x2601
+ devAddr: `2601${key(2)}`,
+ };
+
+ return app.registerDevice(boxId, deviceOpts)
+ // still need to fetch the device data
+ .then(() => app.device(boxId));
+ });
+};
+
+module.exports = {
+ getMqtt,
+ getApp,
+ getOrRegisterDevice,
+};
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
diff --git a/docs/module-database.html b/docs/module-database.html
new file mode 100644
index 0000000..ccde804
--- /dev/null
+++ b/docs/module-database.html
@@ -0,0 +1,605 @@
+
+
+
+
+ JSDoc: Module: database
+
+
+
+
+
+
+
+
+
+
+
+
+
Module: database
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Handles operations on the opensensemap-api database using mongoose.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ License:
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+
+ (inner) boxFromBoxId(id, optsopt ) → {Promise.<Box>}
+
+
+
+
+
+
+
+ look up a Box in the database for a given boxID.
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+ Default
+
+
+ Description
+
+
+
+
+
+
+
+
+ id
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The ID of the box to retrieve.
+
+
+
+
+
+
+ opts
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ { populate: false, lean: true }
+
+
+
+
+ Query options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise.<Box>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (inner) boxFromDevId(app_id, dev_id, portopt ) → {Promise.<Box>}
+
+
+
+
+
+
+
+ look up a Box in the database for a given ttn configuration
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ app_id
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dev_id
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ port
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise.<Box>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-decoding.html b/docs/module-decoding.html
index 5ee63cb..a84b222 100644
--- a/docs/module-decoding.html
+++ b/docs/module-decoding.html
@@ -94,7 +94,7 @@ Module: decoding
Source:
@@ -120,6 +120,8 @@ Module: decoding
+
+
@@ -130,13 +132,85 @@ Module: decoding
-
+
+ Members
+
+
+
+(inner, constant) decodeJSON
+
+
+
+
+
+ Decodes multiple json encoded measurements and validates them.
+Accepts either an measurement array, or an object like
+ {"sensorId": [value, time, location]}
+refer to
oSeM docs
+for specific input formats.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Methods
@@ -144,7 +218,9 @@ Methods
+
(private, inner) bufferToMeasurements(buffer, bufferTransformer) → {Array}
+
@@ -268,7 +344,7 @@ Parameters:
Source:
@@ -315,6 +391,8 @@ Returns:
+
+
Example
Interface of bufferTransformer elements
@@ -324,7 +402,7 @@ Example
sensorId: String, // corresponding sensor_id for this measurement
transformer: Function // function that accepts an Array of bytes and
// returns the measurement value
- onResult: Function // hook that recieves the all decoded measurements
+ onResult: Function // hook that recieves all decoded measurements
// once decoding has finished. may modify the
// measurement array.
}
@@ -336,7 +414,9 @@ Example
+
(inner) decodeBase64(base64String, box, timestampopt ) → {Promise}
+
@@ -526,7 +606,7 @@ Parameters:
Source:
@@ -582,12 +662,16 @@ Returns:
+
+
+
(inner) decodeBuffer(buffer, box, timestampopt ) → {Promise}
+
@@ -778,7 +862,7 @@ Parameters:
Source:
@@ -827,6 +911,248 @@ Returns:
+
+
+
+
+
+
+
+
+ (inner) decodeRequest(payload, box, timeopt ) → {Promise}
+
+
+
+
+
+
+
+ Decodes the a payload to validated measurements for a given box.
+selects the decoder and sensors from `box`, applies `time` if it was not
+provided in the payload
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ payload
+
+
+
+
+
+TTNUplink
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A valid uplink payload object as received from TTN
+
+
+
+
+
+
+ box
+
+
+
+
+
+Box
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The senseBox to match the measurements against
+
+
+
+
+
+
+ time
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+ An ISODate as fallback for measurement timestamps
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+ See:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+ Once fulfilled returns a validated array of measurements
+
+
+
+
+
+
+ Type
+
+
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
@@ -843,13 +1169,13 @@ Returns:
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/module-decoding_debug.html b/docs/module-decoding_debug.html
index d3ba1e0..9e0c7af 100644
--- a/docs/module-decoding_debug.html
+++ b/docs/module-decoding_debug.html
@@ -94,7 +94,7 @@ Module: decoding/debug
Source:
@@ -120,6 +120,8 @@ Module: decoding/debug
+
+
@@ -130,7 +132,9 @@ Module: decoding/debug
-
+
+
+
@@ -144,7 +148,9 @@ Methods
+
+
@@ -244,7 +250,7 @@ Parameters:
Source:
@@ -298,6 +304,8 @@ Returns:
+
+
Example
decodeOptions format
@@ -326,13 +334,13 @@ Example
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/module-decoding_helpers.html b/docs/module-decoding_helpers.html
index 451cccb..1f99e6e 100644
--- a/docs/module-decoding_helpers.html
+++ b/docs/module-decoding_helpers.html
@@ -47,7 +47,9 @@ Module: decoding/helpers
-
+
+
+
@@ -61,7 +63,157 @@ Methods
+
+ (inner) applyValueFromMeasurement(args)
+
+
+
+
+
+
+
+ Factory that returns a bufferTransformer `onResult`-hook, which applies the
+value of the measurement from `sensorId` to the `property` of the following
+measurements, until the measurement with this `sensorId` is found.
+The used measurement is removed from the result.
+If a transformer function is passed, the value will be passed trough the function.
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ args
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+ Parameters as mentioned in description.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Example
+
+ {
+ sensorId: String, // sensorId to match measurement against
+ property: String, // property to assign the value of the matched measure
+ transformer: Function // optional value transformation function
+}
+
+
+
+
+
+
+
+
+
(inner) bytesToInt(bytes) → {Number}
+
@@ -162,7 +314,7 @@ Parameters:
Source:
@@ -206,12 +358,16 @@ Returns:
+
+
+
(inner) findSensorIds(sensors, sensorMatchings) → {Object}
+
@@ -335,7 +491,7 @@ Parameters:
Source:
@@ -383,6 +539,8 @@ Returns:
+
+
Examples
sensorMatchings
@@ -423,13 +581,13 @@ Examples
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/module-decoding_lora-serialization.html b/docs/module-decoding_lora-serialization.html
index 475edbd..d38749d 100644
--- a/docs/module-decoding_lora-serialization.html
+++ b/docs/module-decoding_lora-serialization.html
@@ -90,7 +90,7 @@ Module: decoding/lora-serialization
Source:
@@ -116,120 +116,11 @@ Module: decoding/lora-serialization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Methods
-
-
-
-
- (private, inner) applyTimestamps(measurements)
-
-
-
-
-
-
- transforms the measurements as onResult hook of the
-unixtime decoder IN PLACE
-
-
-
-
-
-
-
-
-
-
- Parameters:
-
-
-
-
-
-
- Name
-
-
- Type
-
-
-
-
-
- Description
-
-
-
-
-
-
-
-
- measurements
-
-
-
-
-
-Array
-
-
-
-
-
-
-
-
-
- The generated measurements of the full payload
- prior to validation.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -239,11 +130,6 @@ Parameters:
-
- Source:
-
@@ -251,29 +137,16 @@ Parameters:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Methods
+
+
@@ -374,7 +247,7 @@ Parameters:
Source:
@@ -428,6 +301,8 @@ Returns:
+
+
Example
decodeOptions format
@@ -462,13 +337,13 @@ Example
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/module-decoding_sensebox_home.html b/docs/module-decoding_sensebox_home.html
index 10f39a6..eb6b815 100644
--- a/docs/module-decoding_sensebox_home.html
+++ b/docs/module-decoding_sensebox_home.html
@@ -92,7 +92,7 @@ Module: decoding/sensebox_home
Source:
@@ -118,6 +118,8 @@ Module: decoding/sensebox_home
+
+
@@ -128,7 +130,9 @@ Module: decoding/sensebox_home
-
+
+
+
@@ -142,7 +146,9 @@ Methods
+
+
@@ -242,7 +248,7 @@ Parameters:
Source:
@@ -297,6 +303,8 @@ Returns:
+
+
@@ -313,13 +321,13 @@ Returns:
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/module-errors-AuthorizationError.html b/docs/module-errors-AuthorizationError.html
new file mode 100644
index 0000000..42da39c
--- /dev/null
+++ b/docs/module-errors-AuthorizationError.html
@@ -0,0 +1,169 @@
+
+
+
+
+ JSDoc: Class: AuthorizationError
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: AuthorizationError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Constructor
+
+
+
+
new AuthorizationError()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-errors-BoxNotFoundError.html b/docs/module-errors-BoxNotFoundError.html
new file mode 100644
index 0000000..26aeb84
--- /dev/null
+++ b/docs/module-errors-BoxNotFoundError.html
@@ -0,0 +1,218 @@
+
+
+
+
+ JSDoc: Class: BoxNotFoundError
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: BoxNotFoundError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Constructor
+
+
+
+
new BoxNotFoundError(message)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ message
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-errors-DecodingError.html b/docs/module-errors-DecodingError.html
new file mode 100644
index 0000000..29d4464
--- /dev/null
+++ b/docs/module-errors-DecodingError.html
@@ -0,0 +1,241 @@
+
+
+
+
+ JSDoc: Class: DecodingError
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: DecodingError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Constructor
+
+
+
+
new DecodingError(message, decoder)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ message
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ decoder
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+ The name of the decoder that is erroring
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-errors-LoraError.html b/docs/module-errors-LoraError.html
new file mode 100644
index 0000000..7befb10
--- /dev/null
+++ b/docs/module-errors-LoraError.html
@@ -0,0 +1,218 @@
+
+
+
+
+ JSDoc: Class: LoraError
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: LoraError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Constructor
+
+
+
+
new LoraError(message)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ message
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-errors-PayloadError.html b/docs/module-errors-PayloadError.html
new file mode 100644
index 0000000..8bc658a
--- /dev/null
+++ b/docs/module-errors-PayloadError.html
@@ -0,0 +1,218 @@
+
+
+
+
+ JSDoc: Class: PayloadError
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: PayloadError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Constructor
+
+
+
+
new PayloadError(message)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ message
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-errors-TTNError.html b/docs/module-errors-TTNError.html
new file mode 100644
index 0000000..4f3d0ce
--- /dev/null
+++ b/docs/module-errors-TTNError.html
@@ -0,0 +1,219 @@
+
+
+
+
+ JSDoc: Class: TTNError
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: TTNError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Constructor
+
+
+
+
new TTNError(message)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ message
+
+
+
+
+
+any
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-errors.html b/docs/module-errors.html
new file mode 100644
index 0000000..f79adbb
--- /dev/null
+++ b/docs/module-errors.html
@@ -0,0 +1,191 @@
+
+
+
+
+ JSDoc: Module: errors
+
+
+
+
+
+
+
+
+
+
+
+
+
Module: errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
All errors handled by the app must inherit from TTNError
+and should be defined here for easier discovery.
+Where applicable, the error subclass should set an HTTP status code
+on `this.code`.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ License:
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+ AuthorizationError
+
+
+ BoxNotFoundError
+
+
+ DecodingError
+
+
+ LoraError
+
+
+ PayloadError
+
+
+ TTNError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-logging.html b/docs/module-logging.html
new file mode 100644
index 0000000..2bb36dc
--- /dev/null
+++ b/docs/module-logging.html
@@ -0,0 +1,358 @@
+
+
+
+
+ JSDoc: Module: logging
+
+
+
+
+
+
+
+
+
+
+
+
+
Module: logging
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Configuration for a bunyan logger to be used in the application.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ License:
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+
+ (private, inner) createLogger(component, optionsopt )
+
+
+
+
+
+
+
+ Creates a bunyan logger that inherits some global settings
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ component
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ name for the logger, stored in the logfield `component`
+
+
+
+
+
+
+ options
+
+
+
+
+
+*
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+ bunyan options to be passed to the logger
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+ a bunyan logger
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-routes_v1_1.html b/docs/module-routes_v1_1.html
index baefe5a..efe7df3 100644
--- a/docs/module-routes_v1_1.html
+++ b/docs/module-routes_v1_1.html
@@ -38,7 +38,8 @@ Module: routes/v1_1
-
Router for TTN HTTP integrations API v1 and this API v1
+
Router for the `/v1.1` prefixed routes, meaning TTN HTTP integrations API v1
+and this API v1.
@@ -90,7 +91,7 @@
Module: routes/v1_1
Source:
@@ -116,6 +117,8 @@
Module: routes/v1_1
+
+
@@ -126,7 +129,9 @@ Module: routes/v1_1
-
+
+
+
@@ -135,6 +140,83 @@ Members
+(inner) 'GET /v1.1/ttndevice/:boxID'
+
+
+
+
+
+ Returns the TTN device options for a senseBox. If no TTN device for this box
+exists, a new device is registered first.
+Requires authentication via a bearer token in the `Authorization` header.
+
+box <-> device matching:
+
+app_id = cfg.ttn.appId,
+dev_id = box._id,
+dev_eui = random,
+app_skey/ nwskey = random
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Example
+
+ curl -H "authorization: Bearer secretkey" \
+ http://localhost:3000/v1.1/ttndevice/59c585b44fe8d35c5787586e
+
+
+
+
+
(inner) 'POST /v1.1'
@@ -143,8 +225,8 @@ (inner) '
Accepts a POST request from the TTN HTTP integrations uplink API, version 1
as specified
here ,
-and decodes it's payload to store a set of measurements for a box.
-The box is identified by it's registered values app_id and dev_id.
+and decodes its payload to store a set of measurements for a box.
+The box is identified by `app_id`, `dev_id` and `port in `box.integrations.ttn`.
If a box specifies a port, it will only recieve measurements sent on that port.
@@ -183,7 +265,7 @@ (inner) '
Source:
@@ -217,14 +299,18 @@ Methods
- (private, inner) handleResponse(req, res, data)
+
+ (private, inner) authOpensensemap()
+
- send responses & do logging
+ Express middleware that checks the `Authorization` header against
+`cfg.authTokens`. Expects a token as defined in `cfg.authTokens`, prefixed
+with `"Bearer "`. Rejects a request with status 403 if not authorized.
@@ -260,13 +346,13 @@ Parameters:
- req
+ req.header.authorization
-Request
+string
@@ -280,16 +366,135 @@ Parameters:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Example
+
+ curl -H "authorization: Bearer secret" localhost:3000/v1.1/ttndevice/boxID
+
+
+
+
+
+
+
+
+
+ (private, inner) httpIntegrationHandler()
+
+
+
+
+
+
+
+ Express middleware which matches a box against the payload, decodes the
+message, and stores the measurements for the matched sensors.
+Stores the matched box in `req.box`.
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
- res
+ req.body
-Response
+string
@@ -299,20 +504,297 @@ Parameters:
-
+ The payload in TTN HTTP Integration v1 format.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (private, inner) registerDeviceHandler()
+
+
+
+
+
+
+
+ Express middleware that tries to match a TTN device to the box given in
+`req.params.boxId`. If no device was found, a new one is registered.
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
- data
+ req.params.boxId
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+ The box to match a device `dev_id` against.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (private, inner) sendResponse()
+
+
+
+
+
+
+
+ Express middleware which finishes a request by sending an API response
+contained in `res.locals.result`.
+Also performs response logging while setting req.locals.responseTime.
+Determines status code by checking the error type.
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ req.locals.result
Object
+|
+
+Error
+
+
+
+
+
+
+
+
+
+ `{ code, message }`
+
+
+
+
+
+
+ req.time
+
+
+
+
+
+Date
@@ -322,7 +804,30 @@ Parameters:
- Format: either String or { code: Number, msg: Any }
+ The timestamp when we received the request.
+
+
+
+
+
+
+ req.box
+
+
+
+
+
+Box
+
+
+
+
+
+
+
+
+
+ The matched box
@@ -363,7 +868,7 @@ Parameters:
Source:
@@ -389,6 +894,8 @@ Parameters:
+
+
@@ -405,13 +912,13 @@ Parameters:
- Modules
+ Modules Classes
- Documentation generated by JSDoc 3.4.3 on Wed May 03 2017 19:20:09 GMT+0200 (CEST)
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
diff --git a/docs/module-ttn.html b/docs/module-ttn.html
new file mode 100644
index 0000000..3d0c3c7
--- /dev/null
+++ b/docs/module-ttn.html
@@ -0,0 +1,953 @@
+
+
+
+
+ JSDoc: Module: ttn
+
+
+
+
+
+
+
+
+
+
+
+
+
Module: ttn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Handles communication with the TTN backend, providing functions for
+retrieval & registration of devices for a TTN application, and sets up a
+MQTT connection, to decode all received uplink messages as measurements.
+This assumes that each device in the application maps to a box, where
+`dev_id === box._id`.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ License:
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+
+ (inner) getApp() → {Promise}
+
+
+
+
+
+
+
+ Initializes and returns a client for the TTN ApplicationManager API for the
+configured app_id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (inner) getMqtt() → {Promise}
+
+
+
+
+
+
+
+ Initializes and returns a client for the TTN MQTT Broker for the
+configured app_id. Enables the subscription to uplink messages on the
+configured application.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (inner) getOrRegisterDevice(boxId, dev_euiopt ) → {Promise}
+
+
+
+
+
+
+
+ Returns a TTN device registered at cfg.ttn.appId. If it does not exist,
+a new device is created with randomized keys and boxId === dev_id.
+IDEA: maintain local device cache, and check it first?
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ boxId
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dev_eui
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+ The EUI of the device if known
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+ resolves with the device data
+
+
+
+
+
+
+ Type
+
+
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (private, inner) initApp() → {Promise}
+
+
+
+
+
+
+
+ Resolves the application at any TTN handler.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (private, inner) initMqtt() → {Promise}
+
+
+
+
+
+
+
+ Connects to the TTN MQTT broker and subscribes to incoming messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (private, inner) onUplinkMessage(dev_id, message)
+
+
+
+
+
+
+
+ Handler for MQTT uplink messages
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ dev_id
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+ the ID of the sending device
+
+
+
+
+
+
+ message
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+ payload object as described here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modules Classes
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Wed Dec 06 2017 15:19:29 GMT+0100 (CET)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/styles/jsdoc-default.css b/docs/styles/jsdoc-default.css
index ede1919..9207bc8 100644
--- a/docs/styles/jsdoc-default.css
+++ b/docs/styles/jsdoc-default.css
@@ -78,6 +78,10 @@ article dl {
margin-bottom: 40px;
}
+article img {
+ max-width: 100%;
+}
+
section
{
display: block;
@@ -218,8 +222,8 @@ thead tr
th { border-right: 1px solid #aaa; }
tr > th:last-child { border-right: 1px solid #ddd; }
-.ancestors { color: #999; }
-.ancestors a
+.ancestors, .attribs { color: #999; }
+.ancestors a, .attribs a
{
color: #999 !important;
text-decoration: none;
diff --git a/lib/database.js b/lib/database.js
new file mode 100644
index 0000000..ce30c9a
--- /dev/null
+++ b/lib/database.js
@@ -0,0 +1,59 @@
+'use strict';
+
+/**
+ * Handles operations on the opensensemap-api database using mongoose.
+ * @module database
+ * @license MIT
+ */
+
+const { Box } = require('openSenseMapAPI').models;
+const { BoxNotFoundError } = require('./errors');
+
+/**
+ * look up a Box in the database for a given ttn configuration
+ * @param {string} app_id
+ * @param {string} dev_id
+ * @param {string} [port]
+ * @returns {Promise}
+ */
+const boxFromDevId = function boxFromDevId (app_id, dev_id, port) {
+ return Box.find({
+ 'integrations.ttn.app_id': app_id,
+ 'integrations.ttn.dev_id': dev_id
+ })
+ .then(boxes => {
+ if (!boxes.length) {
+ throw new BoxNotFoundError(`for dev_id '${dev_id}' and app_id '${app_id}'`);
+ }
+
+ // filter the boxes by their configured port.
+ // also include boxes with undefined port.
+ const box = boxes.filter(box => {
+ const p = box.integrations.ttn.port;
+
+ return (p === port || p === undefined);
+ })[0];
+
+ if (!box) {
+ throw new BoxNotFoundError(`for port ${port}`);
+ }
+
+ return box;
+ });
+};
+
+/**
+ * look up a Box in the database for a given boxID.
+ * @param {string} id - The ID of the box to retrieve.
+ * @param {Object} [opts={ populate: false, lean: true }] - Query options
+ * @returns {Promise}
+ */
+const boxFromBoxId = function boxFromBoxId (id, { populate = false, lean = true } = {}) {
+ return Box.findBoxById(id, { populate, lean })
+ .catch(err => Promise.reject(new BoxNotFoundError(err.message)));
+};
+
+module.exports = {
+ boxFromBoxId,
+ boxFromDevId,
+};
diff --git a/lib/decoding/debug.js b/lib/decoding/debug.js
index 1291ebb..d0f85e3 100644
--- a/lib/decoding/debug.js
+++ b/lib/decoding/debug.js
@@ -11,6 +11,7 @@
*/
const { bytesToInt } = require('./helpers');
+const { DecodingError } = require('../errors');
/**
* returns a bufferTransfomer for transformation of a buffer to measurements.
@@ -29,11 +30,11 @@ const createBufferTransformer = function createBufferTransformer (box) {
transformer = [];
if (!byteMask) {
- throw new Error('profile \'debug\' requires a valid byteMask');
+ throw new DecodingError('box requires a valid byteMask', 'debug');
}
if (box.sensors.length < byteMask.length) {
- throw new Error(`box requires at least ${byteMask.length} sensors`);
+ throw new DecodingError(`box requires at least ${byteMask.length} sensors`, 'debug');
}
for (let i = 0; i < byteMask.length; i++) {
diff --git a/lib/decoding/index.js b/lib/decoding/index.js
index e0f98c3..8826522 100644
--- a/lib/decoding/index.js
+++ b/lib/decoding/index.js
@@ -12,6 +12,7 @@
const { transformAndValidateArray, json } = require('openSenseMapAPI').decoding;
const { createLogger } = require('../logging');
+const { DecodingError } = require('../errors');
const profiles = {
/* eslint-disable global-require */
@@ -50,7 +51,7 @@ const bufferToMeasurements = function bufferToMeasurements (buffer, bufferTransf
}
if (maskLength !== buffer.length) {
- throw new Error(`incorrect amount of bytes: got ${buffer.length}, should be ${maskLength}`);
+ throw new DecodingError(`incorrect amount of bytes: got ${buffer.length}, should be ${maskLength}`);
}
// feed each bufferTransformer element
@@ -88,18 +89,18 @@ const decodeBuffer = function decodeBuffer (buffer, box, timestamp) {
return Promise.resolve().then(function () {
// should never be thrown, as we find a box by it's ttn config
if (!buffer.length) {
- throw new Error('payload may not be empty');
+ throw new DecodingError('payload may not be empty');
}
if (!box.integrations || !box.integrations.ttn) {
- throw new Error('box has no TTN configuration');
+ throw new DecodingError('box has no TTN configuration');
}
// select bufferTransformer according to profile
const profile = profiles[box.integrations.ttn.profile];
if (!profile) {
- throw new Error(`profile '${box.integrations.ttn.profile}' is not supported`);
+ throw new DecodingError(`profile '${box.integrations.ttn.profile}' is not supported`);
}
const bufferTransformer = profile.createBufferTransformer(box);
@@ -135,8 +136,8 @@ const decodeBase64 = function decodeBase64 (base64String, box, timestamp) {
};
/**
- * decodes multiple json encoded measurements and validates them.
- * accepts either an measurement array, or an object like
+ * Decodes multiple json encoded measurements and validates them.
+ * Accepts either an measurement array, or an object like
* {"sensorId": [value, time, location]}
* refer to {@link https://docs.opensensemap.org/#api-Measurements-postNewMeasurements|oSeM docs}
* for specific input formats.
@@ -146,35 +147,41 @@ const decodeBase64 = function decodeBase64 (base64String, box, timestamp) {
const decodeJSON = json.decodeMessage;
/**
- * decodes the request payload in `req.body` to validated measurements,
- * selects the decoder from `req.box` and applies `req.time`
+ * Decodes the a payload to validated measurements for a given box.
+ * selects the decoder and sensors from `box`, applies `time` if it was not
+ * provided in the payload
* @see module:decoding~decodeBuffer
- * @param {Request} req
+ * @param {TTNUplink} payload A valid uplink payload object as received from TTN
+ * @param {Box} box The senseBox to match the measurements against
+ * @param {string} [time] An ISODate as fallback for measurement timestamps
* @return {Promise} Once fulfilled returns a validated array of measurements
*/
-const decodeRequest = function decodeRequest (req) {
+const decodeRequest = function decodeRequest (payload, box, time) {
// extract time from payload: if available use time from
// gateway, else use TTN API time, else use local request time
- let time = req.time.toISOString();
let timeSource = 'local';
- if (req.body.metadata) {
- time = req.body.metadata.time;
+ if (payload.metadata) {
+ time = payload.metadata.time;
timeSource = 'TTN api';
- if (req.body.metadata.gateways && req.body.metadata.gateways[0].time) {
- time = req.body.metadata.gateways[0].time;
+ if (payload.metadata.gateways && payload.metadata.gateways[0].time) {
+ time = payload.metadata.gateways[0].time;
timeSource = 'gateway';
}
}
- log.trace(`using ${timeSource} time & ${req.box.integrations.ttn.profile} decoder`);
+ if (!box.integrations || !box.integrations.ttn) {
+ throw new DecodingError('box has no TTN configuration');
+ }
+
+ log.trace(`using ${timeSource} time & ${box.integrations.ttn.profile} decoder`);
- if (req.box.integrations.ttn.profile === 'json') {
- return req.body.payload_fields
- ? decodeJSON(req.body.payload_fields)
- : Promise.reject('no payload for profile `json` provided');
+ if (box.integrations.ttn.profile === 'json') {
+ return payload.payload_fields
+ ? decodeJSON(payload.payload_fields)
+ : Promise.reject(new DecodingError('no payload for profile `json` provided'));
}
- return decodeBase64(req.body.payload_raw, req.box, time);
+ return decodeBase64(payload.payload_raw, box, time);
};
module.exports = {
diff --git a/lib/decoding/lora-serialization.js b/lib/decoding/lora-serialization.js
index b9cd294..d728775 100644
--- a/lib/decoding/lora-serialization.js
+++ b/lib/decoding/lora-serialization.js
@@ -8,6 +8,7 @@
const loraSerialization = require('lora-serialization').decoder,
{ createLogger } = require('../logging'),
+ { LoraError } = require('../errors'),
{ findSensorIds, applyValueFromMeasurement } = require('./helpers');
const log = createLogger('decoder/lora-serialization');
@@ -41,7 +42,7 @@ const createBufferTransformer = function createBufferTransformer (box) {
byteMask.constructor !== Array ||
!byteMask.every(opts => typeof opts === 'object')
) {
- throw new Error('profile \'lora-serialization\' requires valid decodeOptions');
+ throw new LoraError('profile \'lora-serialization\' requires valid decodeOptions');
}
let expectedSensorCount = byteMask.length;
@@ -67,7 +68,7 @@ const createBufferTransformer = function createBufferTransformer (box) {
if (['unixtime', 'latLng'].includes(el.decoder)) {
expectedSensorCount--;
} else if (!Object.keys(match).length) {
- throw new Error('invalid decodeOptions. requires at least one of [sensor_id, sensor_title, sensor_type]');
+ throw new LoraError('invalid decodeOptions. requires at least one of [sensor_id, sensor_title, sensor_type]');
} else {
sensorMatchings.push(match);
}
@@ -82,7 +83,7 @@ const createBufferTransformer = function createBufferTransformer (box) {
}, 'matched sensors');
if (Object.keys(sensorIds).length !== expectedSensorCount) {
- throw new Error('box does not contain sensors mentioned in byteMask');
+ throw new LoraError('box does not contain sensors mentioned in byteMask');
}
// create the transformer elements for each measurement.
@@ -95,7 +96,7 @@ const createBufferTransformer = function createBufferTransformer (box) {
typeof transformer !== 'function' ||
byteMask[i].decoder === 'decode'
) {
- throw new Error(`'${byteMask[i].decoder}' is not a supported transformer`);
+ throw new LoraError(`'${byteMask[i].decoder}' is not a supported transformer`);
}
const mask = {
diff --git a/lib/decoding/sensebox_home.js b/lib/decoding/sensebox_home.js
index 8faabe8..a3f06a3 100644
--- a/lib/decoding/sensebox_home.js
+++ b/lib/decoding/sensebox_home.js
@@ -9,6 +9,7 @@
*/
const { bytesToInt, findSensorIds } = require('./helpers');
+const { DecodingError } = require('../errors');
// alternative titles recognized for the sensors
const sensorMatchings = {
@@ -39,7 +40,7 @@ const createBufferTransformer = function createBufferTransformer (box) {
const sensorMap = findSensorIds(box.sensors, sensorMatchings);
if (Object.keys(sensorMap).length !== Object.keys(sensorMatchings).length) {
- throw new Error('box does not contain valid sensors for this profile');
+ throw new DecodingError('box does not contain valid sensors for this profile', 'sensebox/home');
}
const transformer = [
diff --git a/lib/errors.js b/lib/errors.js
new file mode 100644
index 0000000..7648a6d
--- /dev/null
+++ b/lib/errors.js
@@ -0,0 +1,81 @@
+'use strict';
+
+/**
+ * All errors handled by the app must inherit from {@link class:TTNError|TTNError}
+ * and should be defined here for easier discovery.
+ * Where applicable, the error subclass should set an HTTP status code
+ * on `this.code`.
+ *
+ * @module errors
+ * @license MIT
+ */
+
+/**
+ * All errors handled by the app must inherit from TTNError
+ * and should be defined here for easier discovery.
+ * @param {any} message
+ */
+class TTNError extends Error { }
+
+/**
+ * Thrown when a request is not authorized.
+ */
+class AuthorizationError extends TTNError {
+ constructor () {
+ super('Not Authorized');
+ this.code = 403;
+ }
+}
+
+/**
+ * Thrown with { code: 404 } when a box was not found in DB.
+ * @param {string} message
+ */
+class BoxNotFoundError extends TTNError {
+ constructor (message) {
+ super(`No box found ${message}`);
+ this.code = 404;
+ }
+}
+
+/**
+ * Thrown with { code: 400 } when the received payload was malformed.
+ * @param {string} message
+ */
+class PayloadError extends TTNError {
+ constructor (message) {
+ super(`Invalid payload: ${message}`);
+ this.code = 400;
+ }
+}
+
+/**
+ * Thrown with { code: 400 } when the received payload content could not be parsed.
+ * @param {string} message
+ * @param {string} decoder The name of the decoder that is erroring
+ */
+class DecodingError extends PayloadError {
+ constructor (message, decoder = 'generic') {
+ super(`${message} (${decoder} decoder)`);
+ this.component = decoder;
+ }
+}
+
+/**
+ * Specialized error for the lora-serialization decoder
+ * @param {string} message
+ */
+class LoraError extends DecodingError {
+ constructor (message) {
+ super(message, 'lora-serialization');
+ }
+}
+
+module.exports = {
+ TTNError,
+ AuthorizationError,
+ BoxNotFoundError,
+ PayloadError,
+ DecodingError,
+ LoraError,
+};
diff --git a/lib/index.js b/lib/index.js
index aa5db61..a8b3d94 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -5,14 +5,15 @@ const express = require('express'),
server = express(),
{ connect, mongoose } = require('openSenseMapAPI').db,
cfg = require('../config'),
+ { getApp, getMqtt } = require('./ttn'),
{ createLogger } = require('./logging'),
v11Router = require('./routes/v1.1');
-const log = createLogger('server');
+const httpLog = createLogger('http');
server.use(function reqLogger (req, res, next) {
req.time = new Date();
- log.debug({ req }, `${req.method} ${req.url} from ${req.ip}`);
+ httpLog.debug({ req }, `${req.method} ${req.url} from ${req.ip}`);
next();
});
@@ -24,8 +25,14 @@ server.use('/v1.1', v11Router);
const msg = `404 Not Found. Available routes:
-POST /v1.1 webhook for messages from the TTN HTTP Integration
- payload format: https://www.thethingsnetwork.org/docs/applications/http/
+POST /v1.1
+ webhook for messages from the TTN HTTP Integration
+ payload format: https://www.thethingsnetwork.org/docs/applications/http/
+
+GET /1.1/ttndevice/:boxId
+ get the TTN device for a given box. must be called once to enable the feature
+ for a box. (alternative to the webhook feature)
+ requires auth.
`;
server.use(function notFoundHandler (req, res, next) {
@@ -38,14 +45,23 @@ server.use(function notFoundHandler (req, res, next) {
next();
});
-// launch server once connected to DB
+// launch server & connect to TTN once connected to DB
mongoose.set('debug', false);
-connect().then(function onDBConnection () {
- server.listen(cfg.port, (err) => {
- if (!err) {
- log.info(`server listening on port ${cfg.port}`);
- } else {
- log.error(err);
- }
+connect()
+ .then(function onDBConnection () {
+ server.listen(cfg.port, (err) => {
+ if (err) {
+ throw err;
+ }
+
+ httpLog.info(`HTTP API listening on port ${cfg.port}`);
+ });
+
+ // app doesnt have to be initialized at startup,
+ // but makes first request faster & catches errors earlier
+ return Promise.all([getApp(), getMqtt()]);
+ })
+ .catch(function (err) {
+ httpLog.fatal({ err });
+ process.exit(1);
});
-});
diff --git a/lib/logging.js b/lib/logging.js
index 92b0a07..1c71189 100644
--- a/lib/logging.js
+++ b/lib/logging.js
@@ -1,5 +1,11 @@
'use strict';
+/**
+ * Configuration for a bunyan logger to be used in the application.
+ * @module logging
+ * @license MIT
+ */
+
const bunyan = require('bunyan');
const cfg = require('../config');
@@ -8,8 +14,17 @@ const logger = bunyan.createLogger({
level: cfg.loglevel,
serializers: {
req: bunyan.stdSerializers.req,
- res: bunyan.stdSerializers.res,
err: bunyan.stdSerializers.err,
+ box (box) {
+ return box
+ ? { id: box._id, sensors: box.sensors, ttn: box.integrations.ttn }
+ : {};
+ },
+ res (res) {
+ const { responseTime, result: { code } } = res.locals;
+
+ return { responseTime, code };
+ },
}
});
@@ -17,7 +32,8 @@ const logger = bunyan.createLogger({
* Creates a bunyan logger that inherits some global settings
* @private
* @param {string} component name for the logger, stored in the logfield `component`
- * @param {*} options bunyan options to be passed to the logger
+ * @param {*} [options] bunyan options to be passed to the logger
+ * @return a bunyan logger
*/
const createLogger = function createLogger (component, options) {
return logger.child(Object.assign({ component }, options));
diff --git a/lib/routes/v1.1.js b/lib/routes/v1.1.js
index 9dafa02..e1e78d2 100644
--- a/lib/routes/v1.1.js
+++ b/lib/routes/v1.1.js
@@ -1,82 +1,77 @@
'use strict';
/**
- * Router for TTN HTTP integrations API v1 and this API v1
+ * Router for the `/v1.1` prefixed routes, meaning TTN HTTP integrations API v1
+ * and this API v1.
* @module routes/v1_1
* @license MIT
*/
const router = require('express').Router(),
- { Box } = require('openSenseMapAPI').models,
+ { boxFromDevId, boxFromBoxId } = require('../database'),
+ { TTNError, AuthorizationError, PayloadError } = require('../errors'),
+ { getOrRegisterDevice } = require('../ttn'),
+ cfg = require('../../config'),
decoder = require('../decoding'),
{ createLogger } = require('../logging');
+const log = createLogger('webhook-v1.1');
+
/**
- * express middleware, sending responses & compute reponse time
+ * Express middleware which finishes a request by sending an API response
+ * contained in `res.locals.result`.
+ * Also performs response logging while setting req.locals.responseTime.
+ * Determines status code by checking the error type.
*
- * expects res.local to contain { code, data } or an Error
- * expects req.time to be a Date
* @private
+ * @param {Object|Error} req.locals.result - `{ code, message }`
+ * @param {Date} req.time - The timestamp when we received the request.
+ * @param {Box} req.box - The matched box
*/
const sendResponse = function sendResponse (req, res, next) {
- let data = res.locals;
-
- // handle undhandled errors
- if (!data.code) {
- data = { code: 501, msg: data };
+ const r = res.locals.result;
+ const { message } = r;
+ let { code } = r;
+ if (
+ (r instanceof Error && !(r instanceof TTNError)) || // filter non TTNError.code values
+ !code
+ ) {
+ code = 500;
}
- data.msg = data.msg.toString();
-
- res.status(data.code).json(data);
+ res.status(code).json({ code, message });
res.locals.responseTime = Date.now() - req.time.getTime();
- next();
-};
-
-const log = createLogger('webhook-v1.1', {
- serializers: {
- box (box) {
- return box
- ? { id: box._id, sensors: box.sensors, ttn: box.integrations.ttn }
- : {};
- },
- res (res) {
- const { msg, responseTime, code } = res.locals;
-
- return { msg, responseTime, code };
- },
- },
-});
-
-/**
- * express middleware, logging result of a webhook request
- * @private
- */
-const logResponse = function logResponse (req, res, next) {
- const { msg, responseTime, code } = res.locals;
- const message = `${code} (${responseTime}ms): ${msg}`;
- if (code >= 500) {
- log.error({ res }, message);
- } else if (code >= 400) {
- log.warn({ res, box: req.box, payload: req.body }, message);
+ if (r instanceof TTNError) {
+ log.warn({
+ res,
+ box: req.box,
+ payload: req.body,
+ url: req.url
+ }, message);
+ } else if (r.code) {
+ log.info({ res, url: req.url }, message);
} else {
- log.info({ res }, message);
+ log.error({
+ err: r,
+ res,
+ box: req.box,
+ payload: req.body,
+ url: req.url,
+ }, message);
+ }
+
+ if (typeof next === 'function') {
+ return next();
}
- next();
};
/**
- * Accepts a POST request from the TTN HTTP integrations uplink API, version 1
- * as specified {@link https://www.thethingsnetwork.org/docs/applications/http/|here},
- * and decodes it's payload to store a set of measurements for a box.
- * The box is identified by it's registered values app_id and dev_id.
- * If a box specifies a port, it will only recieve measurements sent on that port.
- * @name 'POST /v1.1'
- * @example
- * curl -X POST -H "content-type: application/json" -d \
- * '{ "app_id": "asdf", "dev_id": "qwerty", "payload_raw": "kzIrIYzlOycAMgEA" }' \
- * localhost:3000/v1.1
+ * Express middleware which matches a box against the payload, decodes the
+ * message, and stores the measurements for the matched sensors.
+ * Stores the matched box in `req.box`.
+ * @private
+ * @param {string} req.body - The payload in TTN HTTP Integration v1 format.
*/
const httpIntegrationHandler = function httpIntegrationHandler (req, res, next) {
const { app_id, dev_id, payload_raw, payload_fields, port } = req.body;
@@ -88,68 +83,125 @@ const httpIntegrationHandler = function httpIntegrationHandler (req, res, next)
!app_id ||
!(payload_raw || payload_fields)
) {
- Object.assign(res.locals, {
- code: 422,
- msg: 'malformed request: any of [dev_id, app_id, payload_fields, payload_raw] is missing'
- });
+ res.locals.result = new PayloadError('any of [dev_id, app_id, payload_fields, payload_raw] is missing');
return next();
}
// look up box for dev_id & app_id in DB
- Box.find({
- 'integrations.ttn.app_id': app_id,
- 'integrations.ttn.dev_id': dev_id
- }).catch(msg => Promise.reject({ code: 501, msg }))
- .then(boxes => {
- if (!boxes.length) {
- return Promise.reject({ code: 404, msg: `no box found for dev_id '${dev_id}' and app_id '${app_id}'` });
- }
-
- // filter the boxes by their configured port. also include boxes with undefined port.
- req.box = boxes.filter(box => {
- const p = box.integrations.ttn.port;
-
- return (p === port || p === undefined);
- })[0];
-
- if (!req.box) {
- return Promise.reject({ code: 404, msg: `no box found for port ${port}` });
- }
-
- log.debug({ box: req.box }, 'matched box');
+ boxFromDevId(app_id, dev_id, port)
+ .then(box => {
+ log.debug({ box }, 'matched box');
+ req.box = box;
// decode measurements from request.body, req.box & req.time
- return decoder.decodeRequest(req)
- .catch(msg => Promise.reject({ code: 422, msg }));
+ return decoder.decodeRequest(req.body, box, req.time.toISOString());
})
// store measurements in DB
.then(measurements => {
log.debug({ measurements }, 'resulting measurements');
- return req.box.saveMeasurementsArray(measurements)
- .catch(msg => Promise.reject({ code: 422, msg }));
+ return req.box.saveMeasurementsArray(measurements);
})
.then(() => {
- Object.assign(res.locals, { code: 201, msg: 'measurements created' });
+ res.locals.result = { code: 201, message: 'measurements created' };
return next();
})
- // handle any error passed in
.catch(err => {
- res.locals = err;
+ res.locals.result = err;
return next();
});
};
+/**
+ * Express middleware that checks the `Authorization` header against
+ * `cfg.authTokens`. Expects a token as defined in `cfg.authTokens`, prefixed
+ * with `"Bearer "`. Rejects a request with status 403 if not authorized.
+ * @private
+ * @param {string} req.header.authorization
+ * @example
+ * curl -H "authorization: Bearer secret" localhost:3000/v1.1/ttndevice/boxID
+ */
+const authOpensensemap = function authOpensensemap (req, res, next) {
+ const token = req.header('authorization');
+ if (token && cfg.authTokens.includes(token.split(' ')[1])) {
+ return next();
+ }
+
+ res.locals.result = new AuthorizationError();
+ sendResponse(req, res);
+};
+
+/**
+ * Express middleware that tries to match a TTN device to the box given in
+ * `req.params.boxId`. If no device was found, a new one is registered.
+ * @private
+ * @param {string} req.params.boxId - The box to match a device `dev_id` against.
+ */
+const registerDeviceHandler = function registerDeviceHandler (req, res, next) {
+ const { boxId } = req.params;
+
+ // make sure the box actually exists
+ return boxFromBoxId(boxId)
+ .then(box => {
+ req.box = box; // for the logger in sendResponse()
+ const dev_eui = box.integrations.ttn ? box.integrations.ttn.dev_eui : '';
+
+ return getOrRegisterDevice(boxId, dev_eui);
+ })
+ .then(device => {
+ res.locals.result = { code: 200, message: device };
+ next();
+ })
+ .catch(err => {
+ res.locals.result = err;
+ next();
+ });
+};
+
+/**
+ * Accepts a POST request from the TTN HTTP integrations uplink API, version 1
+ * as specified {@link https://www.thethingsnetwork.org/docs/applications/http/|here},
+ * and decodes its payload to store a set of measurements for a box.
+ * The box is identified by `app_id`, `dev_id` and `port in `box.integrations.ttn`.
+ * If a box specifies a port, it will only recieve measurements sent on that port.
+ * @name 'POST /v1.1'
+ * @example
+ * curl -X POST -H "content-type: application/json" -d \
+ * '{ "app_id": "asdf", "dev_id": "qwerty", "payload_raw": "kzIrIYzlOycAMgEA" }' \
+ * localhost:3000/v1.1
+ */
router.post('/', [
httpIntegrationHandler,
sendResponse,
- logResponse,
+]);
+
+/**
+ * Returns the TTN device options for a senseBox. If no TTN device for this box
+ * exists, a new device is registered first.
+ * Requires authentication via a bearer token in the `Authorization` header.
+ *
+ * box <-> device matching:
+ *
+ * app_id = cfg.ttn.appId,
+ * dev_id = box._id,
+ * dev_eui = random,
+ * app_skey/ nwskey = random
+ *
+ * @name 'GET /v1.1/ttndevice/:boxID'
+ * @example
+ * curl -H "authorization: Bearer secretkey" \
+ http://localhost:3000/v1.1/ttndevice/59c585b44fe8d35c5787586e
+ */
+router.get('/ttndevice/:boxId', [
+ authOpensensemap,
+ registerDeviceHandler,
+ sendResponse,
]);
module.exports = router;
diff --git a/lib/ttn.js b/lib/ttn.js
new file mode 100644
index 0000000..d61b440
--- /dev/null
+++ b/lib/ttn.js
@@ -0,0 +1,185 @@
+'use strict';
+
+/**
+ * Handles communication with the TTN backend, providing functions for
+ * retrieval & registration of devices for a TTN application, and sets up a
+ * MQTT connection, to decode all received uplink messages as measurements.
+ * This assumes that each device in the application maps to a box, where
+ * `dev_id === box._id`.
+ * @module ttn
+ * @license MIT
+ */
+
+const { data, application, key } = require('ttn');
+
+const cfg = require('../config');
+const { boxFromBoxId } = require('./database');
+const { TTNError } = require('./errors');
+const decoder = require('./decoding');
+const { createLogger } = require('./logging');
+
+const log = createLogger('ttn');
+
+/**
+ * Handler for MQTT uplink messages
+ * @private
+ * @param {string} dev_id - the ID of the sending device
+ * @param {Object} message - payload object as described {@link https://www.thethingsnetwork.org/docs/applications/mqtt/api.html|here}
+ */
+const onUplinkMessage = function onUplinkMessage (dev_id, message) {
+ let matchBox;
+
+ // match associated box via dev_id === box._id
+ boxFromBoxId(dev_id, { lean: false })
+ // decode measurements from request.body, req.box & req.time
+ .then(box => {
+ log.debug({ box }, `matched box for dev_id ${dev_id}`);
+ matchBox = box;
+
+ return decoder.decodeRequest(message, box);
+ })
+ // store result in DB
+ .then(measurements => {
+ log.debug({ measurements }, 'resulting measurements');
+
+ return matchBox.saveMeasurementsArray(measurements);
+ })
+ .then(() => log.info(`saved measurements for ${dev_id}`))
+ .catch(err => {
+ if (err instanceof TTNError) {
+ log.warn({ err }, 'could not handle uplink message');
+ } else {
+ log.error({ err });
+ }
+ });
+};
+
+/**
+ * Connects to the TTN MQTT broker and subscribes to incoming messages.
+ * @private
+ * @return {Promise}
+ */
+const initMqtt = function initMqtt () {
+ mqttClient = data(cfg.ttn.appId, cfg.ttn.key)
+ .then(client => {
+ client.on('error', function onTTNError (err) {
+ log.error({ err }, 'could not connect to TTN');
+ });
+
+ client.on('connect', function onTTNConnection () {
+ log.info(`MQTT connected to TTN app ${cfg.ttn.appId}`);
+ });
+
+ client.on('disconnect', function onTTNConnectionLost () {
+ log.error(`MQTT connection lost for TTN app ${cfg.ttn.appId}, reconnecting..`);
+ });
+
+ client.on('uplink', onUplinkMessage);
+
+ return client;
+ });
+
+ return mqttClient;
+};
+
+/**
+ * Resolves the application at any TTN handler.
+ * @private
+ * @return {Promise}
+ */
+const initApp = function initApp () {
+ const { appId, key } = cfg.ttn;
+ appClient = application(appId, key)
+ .then(client => {
+ // to register devices, we need the apps EUI,
+ // which we fetch once from the account server
+ return client.getEUIs()
+ .then(euis => {
+ log.info({ euis }, `resolved TTN app ${appId}`);
+ client.appEui = euis[0];
+
+ return client;
+ });
+ });
+
+ return appClient;
+};
+
+// getters that return a Promise for the clients but initialize them only once
+let mqttClient, appClient;
+
+/**
+ * Initializes and returns a client for the TTN ApplicationManager API for the
+ * configured app_id
+ * @returns {Promise}
+ */
+const getApp = () => appClient || initApp();
+
+/**
+ * Initializes and returns a client for the TTN MQTT Broker for the
+ * configured app_id. Enables the subscription to uplink messages on the
+ * configured application.
+ * @returns {Promise}
+ */
+const getMqtt = () => mqttClient || initMqtt();
+
+/**
+ * Returns a TTN device registered at cfg.ttn.appId. If it does not exist,
+ * a new device is created with randomized keys and boxId === dev_id.
+ * IDEA: maintain local device cache, and check it first?
+ * @param {string} boxId
+ * @param {string} [dev_eui] - The EUI of the device if known
+ * @return {Promise} resolves with the device data
+ */
+const getOrRegisterDevice = function getOrRegisterDevice (boxId, dev_eui) {
+
+ let app;
+
+ return getApp()
+ .then(ttnApp => {
+ app = ttnApp;
+
+ return app.device(boxId);
+ })
+ // the device likely wasn't found, let's create it
+ .catch(err => {
+ if (err.message !== `handler:device:${cfg.ttn.appId}:${boxId} not found`) {
+ throw new Error(`could not find a device for box ${boxId}: ${err.message}`);
+ }
+
+ log.info(`box ${boxId} has no TTN device, registering a new one`);
+
+ // get a device address for ABP fallback from the Network Server
+ return app.getDeviceAddress(['abp'])
+ .then(devAddr => {
+ // as the enduser has no control over this, we
+ // apply settings that work for most situations
+ const deviceOpts = {
+ // devEui is normally assigned to hardware by lora chip vendor.
+ // if that value is unknown, we generate a pseudo unique ID.
+ // user generated euis must be prefixed with 0x00.
+ // NOTE: only ABP will work in this case!
+ devEui: dev_eui || `00${key(7)}`,
+ appEui: app.appEui,
+ disableFCntCheck: true,
+
+ appKey: key(16), // OTAA key
+
+ // ABP keys. OTAA should be used, but we assign keys just in case
+ nwkSKey: key(16),
+ appSKey: key(16),
+ devAddr,
+ };
+
+ return app.registerDevice(boxId, deviceOpts);
+ })
+ // still need to fetch the device data
+ .then(() => app.device(boxId));
+ });
+};
+
+module.exports = {
+ getMqtt,
+ getApp,
+ getOrRegisterDevice,
+};
diff --git a/package.json b/package.json
index df6ab33..e3e6125 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"start:rawlogs": "node lib/index.js",
"watch": "node-dev --respawn lib/index.js",
"test": "nyc --check coverage --lines 98 --functions 90 --branches 98 mocha test",
- "lint": "eslint *.js lib test",
+ "lint": "eslint config.js lib test",
"docs": "jsdoc -p README.md *.js lib/** -d docs"
},
"repository": {
@@ -36,7 +36,8 @@
"bunyan": "^1.8.12",
"express": "^4.15.4",
"lora-serialization": "^3.0.1",
- "openSenseMapAPI": "git://github.com/sensebox/openSenseMap-API.git#7f786e1"
+ "openSenseMapAPI": "git://github.com/sensebox/openSenseMap-API.git#7f786e1",
+ "ttn": "^2.3.1"
},
"devDependencies": {
"chai": "^4.1.1",
diff --git a/test/00decoder.js b/test/00decoder.js
index ade3a6e..bf0f4c9 100644
--- a/test/00decoder.js
+++ b/test/00decoder.js
@@ -86,7 +86,7 @@ describe('decoder', () => {
.then(decodings => {
// clean up result invariants
for (let i = 0; i < decodings.length; i++) {
- console.log(decodings[i]);
+ //console.log(decodings[i]);
if (i === 7) {continue;}
decodings[i].map(m => { delete m._id; delete m.createdAt; });
}
@@ -135,6 +135,8 @@ describe('decoder', () => {
});
});
+ // TODO: test decodeRequest()
+
describe('profile: debug', () => {
@@ -167,7 +169,7 @@ describe('decoder', () => {
delete p.box.integrations.ttn.decodeOptions;
return expect(decoder.decodeBase64(p.payloads.base64, p.box))
- .to.be.rejectedWith('profile \'debug\' requires a valid byteMask');
+ .to.be.rejectedWith('box requires a valid byteMask');
});
});
@@ -273,7 +275,7 @@ describe('decoder', () => {
.to.equal(new Date('2017-04-20T20:20:31.000Z').getTime());
expect(p2.results.base64[1].location[0]).to.equal(8);
expect(p2.results.base64[1].location[1]).to.equal(52);
-
+
// this is the first measurement in the payload, but returned
// measurements are ordered by date
const timeDiff = new Date().getTime() - p2.results.base64[2].createdAt.valueOf();
diff --git a/test/01server.js b/test/01server.js
index c737733..f8321b2 100644
--- a/test/01server.js
+++ b/test/01server.js
@@ -16,12 +16,10 @@ describe('server runs', () => {
it('should send list of available routes with 404', () => {
return chakram.get(BASE_URL).then(res => {
expect(res).to.have.status(404);
- expect(res.body).to.have.contain('Available routes:');
-
+ expect(res.body).to.contain('Available routes:');
+
return chakram.wait();
});
});
- // TODO: check for existence of headers
-
});
diff --git a/test/02endpoint_v1.1.js b/test/02endpoint_v1.1.js
deleted file mode 100644
index cf2a9c9..0000000
--- a/test/02endpoint_v1.1.js
+++ /dev/null
@@ -1,194 +0,0 @@
-'use strict';
-
-/* eslint-env mocha */
-
-const chakram = require('chakram'),
- expect = chakram.expect;
-
-chakram.addRawPlugin('one', require('chai-things'));
-
-const cfg = require('../config'),
- { connect, mongoose } = require('openSenseMapAPI').db,
- { Box, Measurement } = require('openSenseMapAPI').models;
-
-// test data
-const BASE_URL = `http://localhost:${cfg.port}`,
- box_sbhome = require('./data/ttnBox_sbhome.json'),
- box_json = require('./data/ttnBox_json.json'),
- TTNpayload_sbhome_valid = require('./data/TTNpayload_sbhome_valid.json'),
- TTNpayload_sbhome_nonexistent = require('./data/TTNpayload_sbhome_nonexistent.json'),
- TTNpayload_json_valid = require('./data/TTNpayload_json_valid.json');
-
-
-describe('TTN HTTP Integration v1.1 webhook', () => {
-
- describe('POST /', () => {
- const URL = `${BASE_URL}/v1.1`;
- let measurementCountBefore;
-
- const removeBox = function removeBox (dev_id) {
- return Box.findOne({ 'integrations.ttn.dev_id': dev_id })
- .then(function (box) {
- return box ? box.removeSelfAndMeasurements() : Promise.resolve();
- });
- };
-
- before(function (done) {
- this.timeout(10000);
-
- // wait for DB connection
- mongoose.set('debug', false);
- connect()
- // delete all testboxes
- .then(() => Promise.all([
- removeBox(TTNpayload_sbhome_nonexistent.dev_id),
- removeBox(TTNpayload_sbhome_valid.dev_id),
- removeBox(TTNpayload_json_valid.dev_id)
- ]))
- // reinsert testboxes
- .then(() => Box.initNew(box_sbhome))
- .then(() => Box.initNew(box_json))
- // get initial count of measurements and set payload
- // dynamically, as we need the sensorId and a recent date!
- .then((jsonbox) => {
- TTNpayload_json_valid.payload_fields[jsonbox.sensors[0]._id] = 55.5;
-
- return Measurement.count({});
- })
- .then(count => {
- measurementCountBefore = count;
- done();
- });
- });
-
- it('should exist', () => {
- return chakram.post(URL).then(res => {
- expect(res).to.have.not.status(404);
-
- return chakram.wait();
- });
- });
-
- it('should respond 422 for empty request payloads', () => {
- return chakram.post(URL, {}).then(res => {
- expect(res).to.have.status(422);
-
- return chakram.wait();
- });
-
- });
-
- it('should respond 404 for nonexistent boxes', () => {
- return chakram.post(URL, TTNpayload_sbhome_nonexistent).then(res => {
- expect(res).to.have.status(404);
-
- return chakram.wait();
- });
- });
-
- it('should respond 201 for valid request payload_raw', () => {
- return chakram.post(URL, TTNpayload_sbhome_valid).then(res => {
- expect(res).to.have.status(201);
-
- return chakram.wait();
- });
- });
-
- it('set createdAt to local time if no metadata is provided', () => {
- return Measurement.find({ sensor_id: box_sbhome.sensors[0]._id }).then(measurements => {
- const timeDiff = Date.now() - measurements[0].createdAt.getTime();
- expect(timeDiff).to.be.below(200);
-
- return chakram.wait();
- });
- });
-
- it('set createdAt to TTN time if available', () => {
- const time = new Date(Date.now() + 20 * 1000);
- TTNpayload_sbhome_valid.metadata = { time: time.toISOString() };
-
- return chakram.post(URL, TTNpayload_sbhome_valid)
- .then(() => Measurement.find({ sensor_id: box_sbhome.sensors[0]._id }))
- .then(measurements => {
- const timeSet = measurements.some(m => time.getTime() === m.createdAt.getTime());
- expect(timeSet).to.equal(true);
-
- return chakram.wait();
- });
- });
-
- it('set createdAt to gateway time if available', () => {
- const time = new Date(Date.now() + 40 * 1000);
- TTNpayload_sbhome_valid.metadata.gateways = [{
- time: time.toISOString()
- }];
-
- return chakram.post(URL, TTNpayload_sbhome_valid)
- .then(() => Measurement.find({ sensor_id: box_sbhome.sensors[0]._id }))
- .then(measurements => {
- const timeSet = measurements.some(m => time.getTime() === m.createdAt.getTime());
- expect(timeSet).to.equal(true);
-
- return chakram.wait();
- });
- });
-
- it('should respond 422 for invalid request payload_raw', () => {
- TTNpayload_sbhome_valid.payload_raw = 'asdf';
-
- return chakram.post(URL, TTNpayload_sbhome_valid).then(res => {
- expect(res).to.have.status(422);
-
- return chakram.wait();
- });
- });
-
- it('should respond 404 for box filtered by port', () => {
- TTNpayload_sbhome_valid.port = 1234;
-
- return chakram.post(URL, TTNpayload_sbhome_valid).then(res => {
- expect(res).to.have.status(404);
-
- return chakram.wait();
- });
- });
-
- it('should respond 201 for valid request payload_fields', () => {
- return chakram.post(URL, TTNpayload_json_valid).then(res => {
- expect(res).to.have.status(201);
-
- return chakram.wait();
- });
- });
-
- it('should only parse `payload_fields` when profile `json` is specified', () => {
- TTNpayload_json_valid.dev_id = 'my-dev-id'; // change to box with sensebox/home profile
-
- return chakram.post(URL, TTNpayload_json_valid).then(res => {
- expect(res).to.have.status(422);
- TTNpayload_json_valid.dev_id = 'jsonttnbox';
-
- return chakram.wait();
- });
- });
-
- it('should respond 422 for invalid request payload_fields', () => {
- delete TTNpayload_json_valid.payload_fields;
-
- return chakram.post(URL, TTNpayload_json_valid).then(res => {
- expect(res).to.have.status(422);
-
- return chakram.wait();
- });
- });
-
- it('should add measurements to the database', () => {
- return Measurement.count({}).then(countAfter => {
- expect(countAfter).to.equal(measurementCountBefore + 16); // 3*5 sbhome + 1 json
-
- return chakram.wait();
- });
- });
- });
-
-});
diff --git a/test/02v1_1_httpintegration-hook.js b/test/02v1_1_httpintegration-hook.js
new file mode 100644
index 0000000..bf904cc
--- /dev/null
+++ b/test/02v1_1_httpintegration-hook.js
@@ -0,0 +1,195 @@
+'use strict';
+
+/* eslint-env mocha */
+
+const chakram = require('chakram'),
+ expect = chakram.expect;
+
+chakram.addRawPlugin('one', require('chai-things'));
+
+const cfg = require('../config'),
+ { connect, mongoose } = require('openSenseMapAPI').db,
+ { Box, Measurement } = require('openSenseMapAPI').models;
+
+// test data
+const BASE_URL = `http://localhost:${cfg.port}/v1.1`,
+ box_sbhome = require('./data/ttnBox_sbhome.json'),
+ box_json = require('./data/ttnBox_json.json'),
+ TTNpayload_sbhome_valid = require('./data/TTNpayload_sbhome_valid.json'),
+ TTNpayload_sbhome_nonexistent = require('./data/TTNpayload_sbhome_nonexistent.json'),
+ TTNpayload_json_valid = require('./data/TTNpayload_json_valid.json');
+
+
+describe('POST /v1.1 -- TTN HTTP Integration webhook', () => {
+ const URL = BASE_URL;
+ let measurementCountBefore;
+
+ const removeBox = function removeBox (dev_id) {
+ return Box.findOne({ 'integrations.ttn.dev_id': dev_id })
+ .then(function (box) {
+ return box ? box.removeSelfAndMeasurements() : Promise.resolve();
+ });
+ };
+
+ before(function (done) {
+ this.timeout(10000);
+
+ // wait for DB connection
+ mongoose.set('debug', false);
+ connect()
+ // delete all testboxes
+ .then(() => removeBox(TTNpayload_sbhome_nonexistent.dev_id))
+ // reinsert testboxes
+ .then(() => Box.initNew(box_sbhome))
+ .then(() => Box.initNew(box_json))
+ // get initial count of measurements and set payload
+ // dynamically, as we need the sensorId and a recent date!
+ .then((jsonbox) => {
+ TTNpayload_json_valid.payload_fields[jsonbox.sensors[0]._id] = 55.5;
+
+ return Measurement.count({});
+ })
+ .then(count => {
+ measurementCountBefore = count;
+ done();
+ });
+ });
+
+ after(done => {
+ Promise.all([
+ removeBox(TTNpayload_sbhome_nonexistent.dev_id),
+ removeBox(TTNpayload_sbhome_valid.dev_id),
+ removeBox(TTNpayload_json_valid.dev_id)
+ ]).then(() => done());
+ });
+
+ it('should exist', () => {
+ return chakram.post(URL).then(res => {
+ expect(res).to.have.not.status(404);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 400 for empty request payloads', () => {
+ return chakram.post(URL, {}).then(res => {
+ expect(res).to.have.status(400);
+
+ return chakram.wait();
+ });
+
+ });
+
+ it('should respond 404 for nonexistent boxes', () => {
+ return chakram.post(URL, TTNpayload_sbhome_nonexistent).then(res => {
+ expect(res).to.have.status(404);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 201 for valid request payload_raw', () => {
+ return chakram.post(URL, TTNpayload_sbhome_valid).then(res => {
+ expect(res).to.have.status(201);
+
+ return chakram.wait();
+ });
+ });
+
+ it('set createdAt to local time if no metadata is provided', () => {
+ return Measurement.find({ sensor_id: box_sbhome.sensors[0]._id }).then(measurements => {
+ const timeDiff = Date.now() - measurements[0].createdAt.getTime();
+ expect(timeDiff).to.be.below(200);
+
+ return chakram.wait();
+ });
+ });
+
+ it('set createdAt to TTN time if available', () => {
+ const time = new Date(Date.now() + 20 * 1000);
+ TTNpayload_sbhome_valid.metadata = { time: time.toISOString() };
+
+ return chakram.post(URL, TTNpayload_sbhome_valid)
+ .then(() => Measurement.find({ sensor_id: box_sbhome.sensors[0]._id }))
+ .then(measurements => {
+ const timeSet = measurements.some(m => time.getTime() === m.createdAt.getTime());
+ expect(timeSet).to.equal(true);
+
+ return chakram.wait();
+ });
+ });
+
+ it('set createdAt to gateway time if available', () => {
+ const time = new Date(Date.now() + 40 * 1000);
+ TTNpayload_sbhome_valid.metadata.gateways = [{
+ time: time.toISOString()
+ }];
+
+ return chakram.post(URL, TTNpayload_sbhome_valid)
+ .then(() => Measurement.find({ sensor_id: box_sbhome.sensors[0]._id }))
+ .then(measurements => {
+ const timeSet = measurements.some(m => time.getTime() === m.createdAt.getTime());
+ expect(timeSet).to.equal(true);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 400 for invalid request payload_raw', () => {
+ TTNpayload_sbhome_valid.payload_raw = 'asdf';
+
+ return chakram.post(URL, TTNpayload_sbhome_valid).then(res => {
+ expect(res).to.have.status(400);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 404 for box filtered by port', () => {
+ TTNpayload_sbhome_valid.port = 1234;
+
+ return chakram.post(URL, TTNpayload_sbhome_valid).then(res => {
+ expect(res).to.have.status(404);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 201 for valid request payload_fields', () => {
+ return chakram.post(URL, TTNpayload_json_valid).then(res => {
+ expect(res).to.have.status(201);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should only parse `payload_fields` when profile `json` is specified', () => {
+ TTNpayload_json_valid.dev_id = 'my-dev-id'; // change to box with sensebox/home profile
+
+ return chakram.post(URL, TTNpayload_json_valid).then(res => {
+ expect(res).to.have.status(400);
+ TTNpayload_json_valid.dev_id = 'jsonttnbox';
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 400 for invalid request payload_fields', () => {
+ delete TTNpayload_json_valid.payload_fields;
+
+ return chakram.post(URL, TTNpayload_json_valid).then(res => {
+ expect(res).to.have.status(400);
+
+ return chakram.wait();
+ });
+ });
+
+ it('should add measurements to the database', () => {
+ return Measurement.count({}).then(countAfter => {
+ expect(countAfter).to.equal(measurementCountBefore + 16); // 3*5 sbhome + 1 json
+
+ return chakram.wait();
+ });
+ });
+
+});
diff --git a/test/03v1_1_ttndevice.js b/test/03v1_1_ttndevice.js
new file mode 100644
index 0000000..15461b2
--- /dev/null
+++ b/test/03v1_1_ttndevice.js
@@ -0,0 +1,98 @@
+'use strict';
+
+/* eslint-env mocha */
+
+const chakram = require('chakram'),
+ expect = chakram.expect;
+
+chakram.addRawPlugin('one', require('chai-things'));
+
+const cfg = require('../config'),
+ { Box } = require('openSenseMapAPI').models,
+ ttnClient = require('ttn').application;
+
+// test data
+const BASE_URL = `http://localhost:${cfg.port}/v1.1/ttndevice`,
+ box_json = require('./data/ttnBox_json.json');
+
+describe('GET /v1.1/ttndevices/:boxId -- get/register ttn device', () => {
+ const headers = {
+ headers: { authorization: `Bearer ${cfg.authTokens[0]}` }
+ };
+ let newDeviceInfo;
+
+ before(function (done) {
+ this.timeout(10000);
+
+ // insert testboxes
+ Box.initNew(box_json)
+ .then(jsonbox => {
+ box_json._id = jsonbox._id.toString();
+
+ // make shure no ttn devices exist for these boxes
+ return ttnClient(cfg.ttn.appId, cfg.ttn.key);
+ })
+ .then(app => app.deleteDevice(box_json._id).catch(() => {}))
+ .then(() => done());
+ });
+
+ after(function (done) {
+ this.timeout(10000);
+ // remove box and device again
+ ttnClient(cfg.ttn.appId, cfg.ttn.key)
+ .then(app => app.deleteDevice(box_json._id))
+ .then(() => Box.findOne({ '_id': box_json._id }))
+ .then(box => box.removeSelfAndMeasurements())
+ .then(() => done());
+ });
+
+ it('should respond 403 if not authorized', () => {
+ return chakram.get(`${BASE_URL}/asdfasdf`).then(res => {
+ expect(res).to.have.status(403);
+ expect(res.body).to.have.property('code', 403);
+ expect(res.body).to.have.property('message');
+
+ return chakram.wait();
+ });
+ });
+
+ it('should respond 404 for nonexistent boxes', () => {
+ return chakram.get(`${BASE_URL}/asdfasdf`, headers).then(res => {
+ expect(res).to.have.status(404);
+ expect(res.body).to.have.property('code', 404);
+ expect(res.body).to.have.property('message');
+
+ return chakram.wait();
+ });
+ });
+
+ it('should create a TTN device for a box it didnt exist yet', () => {
+ return chakram.get(`${BASE_URL}/${box_json._id}`, headers).then(res => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.have.property('code', 200);
+ expect(res.body).to.have.property('message');
+ expect(res.body.message).to.have.property('devId', box_json._id);
+ expect(res.body.message).to.have.property('appId', cfg.ttn.appId);
+ expect(res.body.message).to.have.property('devEui');
+ expect(res.body.message).to.have.property('appEui');
+ expect(res.body.message).to.have.property('appKey');
+ expect(res.body.message).to.have.property('appSKey');
+ expect(res.body.message).to.have.property('nwkSKey');
+ expect(res.body.message).to.have.property('devAddr');
+
+ newDeviceInfo = res.body.message;
+
+ return chakram.wait();
+ });
+ });
+
+ it('should return existing TTN device info for a given boxID', () => {
+ return chakram.get(`${BASE_URL}/${box_json._id}`, headers).then(res => {
+ expect(res).to.have.status(200);
+ expect(res.body.message).to.deep.equal(newDeviceInfo);
+
+ return chakram.wait();
+ });
+ });
+
+});
diff --git a/test/04mqtt.js b/test/04mqtt.js
new file mode 100644
index 0000000..e2c650c
--- /dev/null
+++ b/test/04mqtt.js
@@ -0,0 +1,195 @@
+'use strict';
+
+/* eslint-env mocha */
+
+const chakram = require('chakram'),
+ expect = chakram.expect,
+ { promisify } = require('util');
+
+chakram.addRawPlugin('one', require('chai-things'));
+
+const cfg = require('../config'),
+ exec = promisify(require('child_process').exec),
+ { Box, Measurement } = require('openSenseMapAPI').models;
+
+
+const simulateUplink = (dev_id, payload) => exec(`ttnctl devices simulate "${dev_id}" "${payload}"`);
+
+const createBox = ttnConfig => ({
+ exposure: 'mobile',
+ location: [11.531, 48.096],
+ model: 'homeWifi',
+ name: 'testbox - ttn mqtt',
+ ttn: Object.assign({ app_id: cfg.ttn.appId, dev_id: 'PLACEHOLDER' }, ttnConfig),
+});
+
+const registerDevice = box => {
+ const url = `http://localhost:${cfg.port}/v1.1/ttndevice/${box._id}`,
+ opts = {
+ headers: { authorization: `Bearer ${cfg.authTokens[0]}` },
+ };
+
+ return chakram.get(url, opts).then(res => {
+ expect(res).to.have.status(200);
+
+ return res.body.message;
+ });
+};
+
+const registerBoxAndDevice = ttnOpts => {
+ const result = { box: {}, device: {} };
+
+ return Box.initNew(createBox(ttnOpts))
+ .then(box => {
+ result.box = box;
+
+ return registerDevice(box);
+ })
+ .then(device => {
+ result.device = device;
+
+ return result;
+ });
+};
+
+const deleteBoxAndDevice = (o) => {
+ return o.box.removeSelfAndMeasurements()
+ .then(() => exec(`echo 'yes' | ttnctl device delete ${o.device.devId}`));
+};
+
+const wait = duration => new Promise(res => { setTimeout(() => res(), duration); });
+
+// HOWTO test end to end behaviour?
+// log messages, connection on startup, log disconnection, reconnect..
+describe('TTN MQTT subscription', () => {
+
+ let box_json, box_sbhome, box_nottn;
+
+ before(function (done) {
+ this.timeout(5000);
+
+ // check if ttnctl is available and ready for use
+ exec(`ttnctl application select "${cfg.ttn.appId}"`)
+ .catch(err => {
+ if (/not found/.test(err.message)) {
+ console.error('ttnctl executable not available, skipping mqtt end2end tests.');
+ } else if (/user login/.test(err.stdout)) {
+ console.error('ttnctl not ready. please log in first with `ttnctl user login`');
+ } else {
+ console.error(err);
+ }
+
+ this.skip(); // this = mocha before() handler
+ throw err;
+ })
+ // insert test boxes
+ .then(() => Promise.all([
+ registerBoxAndDevice({ profile: 'sensebox/home' }),
+ registerBoxAndDevice({ profile: 'json' }),
+ registerBoxAndDevice({}),
+ ]))
+ .then(boxes => {
+ box_sbhome = boxes[0];
+ box_json = boxes[1];
+ box_nottn = boxes[2];
+ done();
+ })
+ .catch(() => done()); // error is expected from setupTtnctl
+ });
+
+ after(function (done) {
+ // if we skipped these tests..
+ if (!(box_sbhome && box_json && box_nottn)) {
+ return done();
+ }
+
+ this.timeout(5000);
+ deleteBoxAndDevice(box_sbhome)
+ .then(() => deleteBoxAndDevice(box_json))
+ .then(() => deleteBoxAndDevice(box_nottn))
+ .then(() => done());
+ });
+
+ it('should store measurements for valid payloads', function () {
+ this.timeout(10000);
+ let measurementCount = null;
+
+ return Measurement.count({})
+ .then(count => { measurementCount = count; })
+ .then(() => simulateUplink(box_sbhome.device.devId, '93322b218ce53b2700320100'))
+ .then(() => simulateUplink(box_sbhome.device.devId, '93322b218ce53b2700320100'))
+ // wait for mqtt message to be received and handled:
+ .then(() => wait(1000))
+ .then(() => Measurement.count({}))
+ .then(count => {
+ expect(count).to.equal(measurementCount + 2 * 5);
+ });
+ });
+
+ // ie: it should not *crash* / throw errors. no way to check this though currently
+ it('should ignore messages with invalid payload', function () {
+ this.timeout(10000);
+ let measurementCount = null;
+
+ return Measurement.count({})
+ .then(count => { measurementCount = count; })
+ .then(() => simulateUplink(box_sbhome.device.devId, '93322b218ce5'))
+ .then(() => simulateUplink(box_sbhome.device.devId, '93322b218ce570032010001122334455'))
+ // wait for mqtt message to be received and handled:
+ .then(() => wait(1000))
+ .then(() => Measurement.count({}))
+ .then(count => {
+ expect(count).to.equal(measurementCount);
+ });
+
+ });
+
+ it('should ignore devices whose box has no valid ttn config', function () {
+ this.timeout(10000);
+ let measurementCount = null;
+
+ return Measurement.count({})
+ .then(count => { measurementCount = count; })
+ .then(() => simulateUplink(box_nottn.device.devId, '012345'))
+ .then(() => simulateUplink(box_nottn.device.devId, '012345'))
+ .then(() => wait(1000))
+ .then(() => Measurement.count({}))
+ .then(count => {
+ expect(count).to.equal(measurementCount);
+ });
+ });
+
+ it('should ignore measurements for boxes with profile: json', function () {
+ this.timeout(10000);
+ let measurementCount = null;
+
+ return Measurement.count({})
+ .then(count => { measurementCount = count; })
+ .then(() => simulateUplink(box_json.device.devId, '012345'))
+ .then(() => simulateUplink(box_json.device.devId, '012345'))
+ .then(() => wait(1000))
+ .then(() => Measurement.count({}))
+ .then(count => {
+ expect(count).to.equal(measurementCount);
+ });
+ });
+
+ it('should ignore devices whose box can\'t be found', function () {
+ this.timeout(10000);
+ let measurementCount = null;
+
+ return Measurement.count({})
+ .then(count => { measurementCount = count; })
+ .then(() => exec('ttnctl devices register testmqttasdf'))
+ .then(() => simulateUplink('testmqttasdf', '012345'))
+ .then(() => simulateUplink('testmqttasdf', '012345'))
+ .then(() => wait(1000))
+ .then(() => Measurement.count({}))
+ .then(count => {
+ expect(count).to.equal(measurementCount);
+
+ return exec('echo yes | ttnctl devices delete testmqttasdf');
+ });
+ });
+
+});
diff --git a/yarn.lock b/yarn.lock
index 66ced8f..c707d6a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -101,8 +101,8 @@
resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.6.0.tgz#0d3f9a218e58d1c5e5deedf467c3321dd61203f3"
"@turf/meta@^4.7.3":
- version "4.7.4"
- resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.7.4.tgz#6de2f1e9890b8f64b669e4b47c09b20893063977"
+ version "4.7.3"
+ resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.7.3.tgz#038970178e90c8e43cf7dfed79d5782298a1080d"
"@turf/simplify@^4.6.0":
version "4.7.3"
@@ -137,11 +137,11 @@ abbrev@1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
-accepts@~1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
+accepts@~1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
dependencies:
- mime-types "~2.1.11"
+ mime-types "~2.1.16"
negotiator "0.6.1"
acorn-jsx@^3.0.0:
@@ -154,9 +154,9 @@ acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-acorn@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
+acorn@^5.2.1:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
affine-hull@^1.0.0:
version "1.0.0"
@@ -175,14 +175,14 @@ ajv@^4.7.0, ajv@^4.9.1:
co "^4.6.0"
json-stable-stringify "^1.0.1"
-ajv@^5.2.0:
- version "5.2.2"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39"
+ajv@^5.3.0:
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
- json-stable-stringify "^1.0.1"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
@@ -253,6 +253,10 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+arguejs@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/arguejs/-/arguejs-0.2.3.tgz#b6f939f5fe0e3cd1f3f93e2aa9262424bf312af7"
+
arr-diff@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
@@ -289,6 +293,13 @@ arrify@^1.0.0, arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+ascli@~1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc"
+ dependencies:
+ colour "~0.7.1"
+ optjs "~3.2.2"
+
asn1@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
@@ -361,6 +372,13 @@ babel-runtime@^6.22.0:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
+babel-runtime@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
babel-template@^6.16.0:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071"
@@ -394,13 +412,17 @@ babel-types@^6.18.0, babel-types@^6.25.0:
lodash "^4.2.0"
to-fast-properties "^1.0.1"
-babylon@^6.17.2, babylon@^6.17.4:
+babylon@7.0.0-beta.19:
+ version "7.0.0-beta.19"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
+
+babylon@^6.17.2:
version "6.17.4"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a"
-babylon@~7.0.0-beta.19:
- version "7.0.0-beta.19"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
+babylon@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
balanced-match@^1.0.0:
version "1.0.0"
@@ -444,27 +466,27 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^3.5.0:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+bluebird@2.10.2:
+ version "2.10.2"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.10.2.tgz#024a5517295308857f14f91f1106fc3b555f446b"
bluebird@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
-body-parser@^1.17.2:
- version "1.17.2"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee"
+body-parser@1.18.2, body-parser@^1.17.2:
+ version "1.18.2"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
dependencies:
- bytes "2.4.0"
- content-type "~1.0.2"
- debug "2.6.7"
- depd "~1.1.0"
- http-errors "~1.6.1"
- iconv-lite "0.4.15"
+ bytes "3.0.0"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "~1.1.1"
+ http-errors "~1.6.2"
+ iconv-lite "0.4.19"
on-finished "~2.3.0"
- qs "6.4.0"
- raw-body "~2.2.0"
+ qs "6.5.1"
+ raw-body "2.3.2"
type-is "~1.6.15"
boom@2.x.x:
@@ -517,9 +539,15 @@ bunyan@^1.8.1, bunyan@^1.8.12:
mv "~2"
safe-json-stringify "~1"
-bytes@2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
+bytebuffer@~5:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd"
+ dependencies:
+ long "~3"
+
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
caching-transform@^1.0.0:
version "1.0.1"
@@ -557,14 +585,10 @@ camelcase@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
-camelcase@^2.0.0:
+camelcase@^2.0.0, camelcase@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
-camelcase@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
-
camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -624,12 +648,12 @@ chai@3.x.x:
type-detect "^1.0.0"
chai@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.1.tgz#66e21279e6f3c6415ff8231878227900e2171b39"
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c"
dependencies:
assertion-error "^1.0.1"
check-error "^1.0.1"
- deep-eql "^2.0.1"
+ deep-eql "^3.0.0"
get-func-name "^2.0.0"
pathval "^1.0.0"
type-detect "^4.0.0"
@@ -665,6 +689,14 @@ chalk@^2.0.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
+chalk@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
+ dependencies:
+ ansi-styles "^3.1.0"
+ escape-string-regexp "^1.0.5"
+ supports-color "^4.0.0"
+
check-error@^1.0.1, check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
@@ -708,7 +740,7 @@ cliui@^2.1.0:
right-align "^0.1.1"
wordwrap "0.0.2"
-cliui@^3.2.0:
+cliui@^3.0.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
dependencies:
@@ -716,6 +748,14 @@ cliui@^3.2.0:
strip-ansi "^3.0.1"
wrap-ansi "^2.0.0"
+cliui@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
+ dependencies:
+ string-width "^2.1.1"
+ strip-ansi "^4.0.0"
+ wrap-ansi "^2.0.0"
+
clone-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-1.0.0.tgz#eae0a2413f55c0942f818c229fefce845d7f3b1c"
@@ -753,6 +793,10 @@ colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
+colour@~0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778"
+
combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@@ -800,9 +844,9 @@ content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
-content-type@~1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
+content-type@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
convert-source-map@^1.3.0:
version "1.5.0"
@@ -858,8 +902,8 @@ csv-generate@^1.0.0:
resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-1.0.0.tgz#bd52886859d0c925f3e51f60f3abed262fa15caf"
csv-parse@^1.2.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-1.2.4.tgz#cbf676e355226625888c6432400b83f07e75cc2e"
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-1.2.3.tgz#466206b51eaf77ccb50c3fadebd098cdeb2c69e7"
csv-stringify@^1.0.0, csv-stringify@^1.0.4:
version "1.0.4"
@@ -903,24 +947,30 @@ debug-log@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f"
-debug@2.6.7, debug@^2.2.0, debug@^2.6.3:
- version "2.6.7"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
- dependencies:
- ms "2.0.0"
-
debug@2.6.8, debug@^2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
dependencies:
ms "2.0.0"
-debug@^2.6.9:
+debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
ms "2.0.0"
+debug@^2.2.0:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ dependencies:
+ ms "2.0.0"
+
debug@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
@@ -941,11 +991,15 @@ deep-eql@^0.1.3:
dependencies:
type-detect "0.1.1"
-deep-eql@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-2.0.2.tgz#b1bac06e56f0a76777686d50c9feb75c2ed7679a"
+deep-eql@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
dependencies:
- type-detect "^3.0.0"
+ type-detect "^4.0.0"
+
+deep-equal@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
deep-extend@~0.4.0:
version "0.4.2"
@@ -967,6 +1021,17 @@ defaults@^1.0.3:
dependencies:
clone "^1.0.2"
+define-properties@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
+ dependencies:
+ foreach "^2.0.5"
+ object-keys "^1.0.8"
+
+defined@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
del@^2.0.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
@@ -987,10 +1052,6 @@ delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-depd@1.1.0, depd@~1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
-
depd@1.1.1, depd@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
@@ -1013,12 +1074,11 @@ diff@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
-doctrine@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
+doctrine@^2.0.2:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
dependencies:
esutils "^2.0.2"
- isarray "^1.0.0"
dtrace-provider@^0.8.1, dtrace-provider@~0.8:
version "0.8.5"
@@ -1066,6 +1126,12 @@ encodeurl@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
+encoding@^0.1.11:
+ version "0.1.12"
+ resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+ dependencies:
+ iconv-lite "~0.4.13"
+
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206"
@@ -1078,6 +1144,24 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"
+es-abstract@^1.5.0:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.2.tgz#25103263dc4decbda60e0c737ca32313518027ee"
+ dependencies:
+ es-to-primitive "^1.1.1"
+ function-bind "^1.1.1"
+ has "^1.0.1"
+ is-callable "^1.1.3"
+ is-regex "^1.0.4"
+
+es-to-primitive@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
+ dependencies:
+ is-callable "^1.1.1"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.1"
+
es6-promise@3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4"
@@ -1119,32 +1203,36 @@ eslint-scope@^3.7.1:
esrecurse "^4.1.0"
estraverse "^4.1.1"
+eslint-visitor-keys@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+
eslint@^4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.4.0.tgz#a3e153e704b64f78290ef03592494eaba228d3bc"
+ version "4.15.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.15.0.tgz#89ab38c12713eec3d13afac14e4a89e75ef08145"
dependencies:
- ajv "^5.2.0"
+ ajv "^5.3.0"
babel-code-frame "^6.22.0"
- chalk "^1.1.3"
+ chalk "^2.1.0"
concat-stream "^1.6.0"
cross-spawn "^5.1.0"
- debug "^2.6.8"
- doctrine "^2.0.0"
+ debug "^3.1.0"
+ doctrine "^2.0.2"
eslint-scope "^3.7.1"
- espree "^3.5.0"
+ eslint-visitor-keys "^1.0.0"
+ espree "^3.5.2"
esquery "^1.0.0"
- estraverse "^4.2.0"
esutils "^2.0.2"
file-entry-cache "^2.0.0"
functional-red-black-tree "^1.0.1"
glob "^7.1.2"
- globals "^9.17.0"
+ globals "^11.0.1"
ignore "^3.3.3"
imurmurhash "^0.1.4"
inquirer "^3.0.6"
is-resolvable "^1.0.0"
js-yaml "^3.9.1"
- json-stable-stringify "^1.0.1"
+ json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0"
lodash "^4.17.4"
minimatch "^3.0.2"
@@ -1152,19 +1240,20 @@ eslint@^4.4.0:
natural-compare "^1.4.0"
optionator "^0.8.2"
path-is-inside "^1.0.2"
- pluralize "^4.0.0"
+ pluralize "^7.0.0"
progress "^2.0.0"
require-uncached "^1.0.3"
semver "^5.3.0"
+ strip-ansi "^4.0.0"
strip-json-comments "~2.0.1"
table "^4.0.1"
text-table "~0.2.0"
-espree@^3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.0.tgz#98358625bdd055861ea27e2867ea729faf463d8d"
+espree@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca"
dependencies:
- acorn "^5.1.1"
+ acorn "^5.2.1"
acorn-jsx "^3.0.0"
esprima@1.0.x, esprima@~1.0.2:
@@ -1196,7 +1285,7 @@ esrecurse@^4.1.0:
estraverse "^4.1.0"
object-assign "^4.0.1"
-estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
+estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
@@ -1212,9 +1301,9 @@ esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-etag@~1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
execa@^0.7.0:
version "0.7.0"
@@ -1241,37 +1330,39 @@ expand-range@^1.8.1:
fill-range "^2.1.0"
express@^4.15.4:
- version "4.15.4"
- resolved "https://registry.yarnpkg.com/express/-/express-4.15.4.tgz#032e2253489cf8fce02666beca3d11ed7a2daed1"
+ version "4.16.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
dependencies:
- accepts "~1.3.3"
+ accepts "~1.3.4"
array-flatten "1.1.1"
+ body-parser "1.18.2"
content-disposition "0.5.2"
- content-type "~1.0.2"
+ content-type "~1.0.4"
cookie "0.3.1"
cookie-signature "1.0.6"
- debug "2.6.8"
+ debug "2.6.9"
depd "~1.1.1"
encodeurl "~1.0.1"
escape-html "~1.0.3"
- etag "~1.8.0"
- finalhandler "~1.0.4"
- fresh "0.5.0"
+ etag "~1.8.1"
+ finalhandler "1.1.0"
+ fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
- parseurl "~1.3.1"
+ parseurl "~1.3.2"
path-to-regexp "0.1.7"
- proxy-addr "~1.1.5"
- qs "6.5.0"
+ proxy-addr "~2.0.2"
+ qs "6.5.1"
range-parser "~1.2.0"
- send "0.15.4"
- serve-static "1.12.4"
- setprototypeof "1.0.3"
+ safe-buffer "5.1.1"
+ send "0.16.1"
+ serve-static "1.13.1"
+ setprototypeof "1.1.0"
statuses "~1.3.1"
type-is "~1.6.15"
- utils-merge "1.0.0"
- vary "~1.1.1"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
extend-object@1.x.x:
version "1.0.0"
@@ -1317,6 +1408,10 @@ fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
+fast-json-stable-stringify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -1354,15 +1449,15 @@ fill-range@^2.1.0:
repeat-element "^1.1.2"
repeat-string "^1.5.2"
-finalhandler@~1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7"
+finalhandler@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
dependencies:
- debug "2.6.8"
+ debug "2.6.9"
encodeurl "~1.0.1"
escape-html "~1.0.3"
on-finished "~2.3.0"
- parseurl "~1.3.1"
+ parseurl "~1.3.2"
statuses "~1.3.1"
unpipe "~1.0.0"
@@ -1381,7 +1476,7 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
-find-up@^2.0.0, find-up@^2.1.0:
+find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
dependencies:
@@ -1396,6 +1491,12 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"
+for-each@~0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"
+ dependencies:
+ is-function "~1.0.0"
+
for-in@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -1406,7 +1507,7 @@ for-own@^0.1.4:
dependencies:
for-in "^1.0.1"
-foreach@~2.0.1:
+foreach@^2.0.5, foreach@~2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
@@ -1433,19 +1534,19 @@ formidable@^1.0.17:
version "1.1.1"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9"
-forwarded@~0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
+forwarded@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
-fresh@0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-fstream-ignore@~1.0.5:
+fstream-ignore@^1.0.5, fstream-ignore@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
dependencies:
@@ -1453,7 +1554,7 @@ fstream-ignore@~1.0.5:
inherits "2"
minimatch "^3.0.0"
-fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10:
+fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2, fstream@~1.0.10:
version "1.0.11"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
dependencies:
@@ -1462,6 +1563,10 @@ fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10:
mkdirp ">=0.5 0"
rimraf "2"
+function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -1567,7 +1672,7 @@ glob@^6.0.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.1.1, glob@^7.1.2:
+glob@^7.1.1, glob@^7.1.2, glob@~7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
@@ -1578,7 +1683,11 @@ glob@^7.1.1, glob@^7.1.2:
once "^1.3.0"
path-is-absolute "^1.0.0"
-globals@^9.0.0, globals@^9.17.0:
+globals@^11.0.1:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4"
+
+globals@^9.0.0:
version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@@ -1593,6 +1702,10 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+google-protobuf@^3.3.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.4.0.tgz#58fbe51555a0a56b2dd3fc6f6d869fa21d57ab0a"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.9:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@@ -1609,6 +1722,16 @@ growly@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
+grpc@^1.4.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.6.0.tgz#2d637d1e58a03c530ebc9bc2dd6c135c24c122cf"
+ dependencies:
+ arguejs "^0.2.3"
+ lodash "^4.17.4"
+ nan "^2.6.2"
+ node-pre-gyp "^0.6.36"
+ protobufjs "^5.0.2"
+
handle-thing@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
@@ -1661,6 +1784,12 @@ has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+has@^1.0.1, has@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
@@ -1670,6 +1799,10 @@ hawk@~3.1.3:
hoek "2.x.x"
sntp "1.x.x"
+he@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+
help-me@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/help-me/-/help-me-1.1.0.tgz#8f2d508d0600b4a456da2f086556e7e5c056a3c6"
@@ -1711,16 +1844,7 @@ http-deceiver@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
-http-errors@~1.6.1:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257"
- dependencies:
- depd "1.1.0"
- inherits "2.0.3"
- setprototypeof "1.0.3"
- statuses ">= 1.3.1 < 2"
-
-http-errors@~1.6.2:
+http-errors@1.6.2, http-errors@~1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
dependencies:
@@ -1745,9 +1869,9 @@ http-signature@~1.1.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
-iconv-lite@0.4.15:
- version "0.4.15"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
+iconv-lite@0.4.19, iconv-lite@~0.4.13:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
iconv-lite@^0.4.17:
version "0.4.18"
@@ -1822,9 +1946,9 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-ipaddr.js@1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0"
+ipaddr.js@1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
is-absolute@^0.2.5:
version "0.2.6"
@@ -1847,6 +1971,14 @@ is-builtin-module@^1.0.0:
dependencies:
builtin-modules "^1.0.0"
+is-callable@^1.1.1, is-callable@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
+
+is-date-object@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
is-dotfile@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
@@ -1885,6 +2017,10 @@ is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+is-function@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
+
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@@ -1958,6 +2094,12 @@ is-property@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+is-regex@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+ dependencies:
+ has "^1.0.1"
+
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
@@ -1974,7 +2116,7 @@ is-resolvable@^1.0.0:
dependencies:
tryit "^1.0.1"
-is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -1982,6 +2124,10 @@ is-supported-regexp-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz#8b520c85fae7a253382d4b02652e045576e13bb8"
+is-symbol@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
+
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -2004,7 +2150,7 @@ is@~0.2.6:
version "0.2.7"
resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562"
-isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+isarray@1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -2036,46 +2182,46 @@ istanbul-lib-coverage@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
-istanbul-lib-hook@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc"
+istanbul-lib-hook@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b"
dependencies:
append-transform "^0.4.0"
-istanbul-lib-instrument@^1.7.4:
- version "1.7.4"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.4.tgz#e9fd920e4767f3d19edc765e2d6b3f5ccbd0eea8"
+istanbul-lib-instrument@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e"
dependencies:
babel-generator "^6.18.0"
babel-template "^6.16.0"
babel-traverse "^6.18.0"
babel-types "^6.18.0"
- babylon "^6.17.4"
+ babylon "^6.18.0"
istanbul-lib-coverage "^1.1.1"
semver "^5.3.0"
-istanbul-lib-report@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9"
+istanbul-lib-report@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425"
dependencies:
istanbul-lib-coverage "^1.1.1"
mkdirp "^0.5.1"
path-parse "^1.0.5"
supports-color "^3.1.2"
-istanbul-lib-source-maps@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c"
+istanbul-lib-source-maps@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c"
dependencies:
- debug "^2.6.3"
+ debug "^3.1.0"
istanbul-lib-coverage "^1.1.1"
mkdirp "^0.5.1"
rimraf "^2.6.1"
source-map "^0.5.3"
-istanbul-reports@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e"
+istanbul-reports@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10"
dependencies:
handlebars "^4.0.3"
@@ -2134,10 +2280,10 @@ jschardet@^1.4.2:
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9"
jsdoc@^3.5.4:
- version "3.5.4"
- resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.5.4.tgz#ceeef7c4bac4335cb10ff41e3a0f58939a534428"
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.5.5.tgz#484521b126e81904d632ff83ec9aaa096708fa4d"
dependencies:
- babylon "~7.0.0-beta.19"
+ babylon "7.0.0-beta.19"
bluebird "~3.5.0"
catharsis "~0.8.9"
escape-string-regexp "~1.0.5"
@@ -2162,6 +2308,10 @@ json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+
json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
@@ -2203,7 +2353,7 @@ jsonwebtoken@^7.0.0:
ms "^2.0.0"
xtend "^4.0.1"
-jsonwebtoken@^7.4.3:
+jsonwebtoken@^7.4.1, jsonwebtoken@^7.4.3:
version "7.4.3"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638"
dependencies:
@@ -2296,15 +2446,6 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
-load-json-file@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
- dependencies:
- graceful-fs "^4.1.2"
- parse-json "^2.2.0"
- pify "^2.0.0"
- strip-bom "^3.0.0"
-
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -2413,6 +2554,10 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lo
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+long@~3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
+
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@@ -2534,15 +2679,25 @@ mime-db@~1.27.0:
version "1.27.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
-mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
+mime-db@~1.30.0:
+ version "1.30.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
+
+mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.7:
version "2.1.15"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
dependencies:
mime-db "~1.27.0"
-mime@1.3.4:
- version "1.3.4"
- resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
+mime-types@~2.1.16:
+ version "2.1.17"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
+ dependencies:
+ mime-db "~1.30.0"
+
+mime@1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
mime@^1.2.11:
version "1.3.6"
@@ -2566,7 +2721,7 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
+minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -2577,8 +2732,8 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
minimist "0.0.8"
mocha@^3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.0.tgz#1328567d2717f997030f8006234bce9b8cd72465"
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d"
dependencies:
browser-stdout "1.3.0"
commander "2.9.0"
@@ -2587,6 +2742,7 @@ mocha@^3.5.0:
escape-string-regexp "1.0.5"
glob "7.1.1"
growl "1.9.2"
+ he "1.1.1"
json3 "3.3.2"
lodash.create "3.1.1"
mkdirp "0.5.1"
@@ -2618,8 +2774,8 @@ mongoose-timestamp@^0.6:
defaults "^1.0.3"
mongoose@^4.11.7:
- version "4.12.1"
- resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.12.1.tgz#fb27c108f940252e448bb1b80b5f8a404108ee28"
+ version "4.11.12"
+ resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.11.12.tgz#48ebd5cad051f6ddfd46648b86a19c7fd30e36db"
dependencies:
async "2.1.4"
bson "~1.0.4"
@@ -2628,7 +2784,7 @@ mongoose@^4.11.7:
mongodb "2.2.31"
mpath "0.3.0"
mpromise "0.5.5"
- mquery "2.3.2"
+ mquery "2.3.1"
ms "2.0.0"
muri "1.2.2"
regexp-clone "0.0.1"
@@ -2675,13 +2831,31 @@ mqtt@^2.12.0:
websocket-stream "^5.0.1"
xtend "^4.0.1"
-mquery@2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/mquery/-/mquery-2.3.2.tgz#e2c60ad117cf080f2efb1ecdd144e7bbffbfca11"
+mqtt@^2.15.0:
+ version "2.15.1"
+ resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-2.15.1.tgz#86fc34e387495df60cd0ccb75cd97743344a4b28"
+ dependencies:
+ commist "^1.0.0"
+ concat-stream "^1.6.0"
+ end-of-stream "^1.1.0"
+ help-me "^1.0.1"
+ inherits "^2.0.3"
+ minimist "^1.2.0"
+ mqtt-packet "^5.4.0"
+ pump "^2.0.0"
+ readable-stream "^2.3.3"
+ reinterval "^1.1.0"
+ split2 "^2.1.1"
+ websocket-stream "^5.0.1"
+ xtend "^4.0.1"
+
+mquery@2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/mquery/-/mquery-2.3.1.tgz#9ab36749714800ff0bb53a681ce4bc4d5f07c87b"
dependencies:
- bluebird "^3.5.0"
- debug "^2.6.9"
- regexp-clone "^0.0.1"
+ bluebird "2.10.2"
+ debug "2.6.8"
+ regexp-clone "0.0.1"
sliced "0.0.5"
ms@0.7.1:
@@ -2716,6 +2890,10 @@ nan@^2.3.3:
version "2.6.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
+nan@^2.6.2:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
+
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -2745,6 +2923,13 @@ node-emoji@^1.4.1:
dependencies:
string.prototype.codepointat "^0.2.0"
+node-fetch@^1.7.3:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+ dependencies:
+ encoding "^0.1.11"
+ is-stream "^1.0.1"
+
node-notifier@^4.0.2:
version "4.6.1"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-4.6.1.tgz#056d14244f3dcc1ceadfe68af9cff0c5473a33f3"
@@ -2771,6 +2956,21 @@ node-pre-gyp@0.6.32:
tar "~2.2.1"
tar-pack "~3.3.0"
+node-pre-gyp@^0.6.36:
+ version "0.6.37"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.37.tgz#3c872b236b2e266e4140578fe1ee88f693323a05"
+ dependencies:
+ mkdirp "^0.5.1"
+ nopt "^4.0.1"
+ npmlog "^4.0.2"
+ rc "^1.1.7"
+ request "^2.81.0"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tape "^4.6.3"
+ tar "^2.2.1"
+ tar-pack "^3.4.0"
+
nomnom@1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.5.2.tgz#f4345448a853cfbd5c0d26320f2477ab0526fe2f"
@@ -2778,6 +2978,13 @@ nomnom@1.5.2:
colors "0.5.x"
underscore "1.1.x"
+nopt@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
+ dependencies:
+ abbrev "1"
+ osenv "^0.1.4"
+
nopt@~3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@@ -2805,7 +3012,7 @@ npm-run-path@^2.0.0:
dependencies:
path-key "^2.0.0"
-npmlog@^4.0.1:
+npmlog@^4.0.1, npmlog@^4.0.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
dependencies:
@@ -2819,8 +3026,8 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
nyc@^11.1.0:
- version "11.1.0"
- resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.1.0.tgz#d6b3c5e16892a25af63138ba484676aa8a22eda7"
+ version "11.4.1"
+ resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.4.1.tgz#13fdf7e7ef22d027c61d174758f6978a68f4f5e5"
dependencies:
archy "^1.0.0"
arrify "^1.0.1"
@@ -2833,11 +3040,11 @@ nyc@^11.1.0:
foreground-child "^1.5.3"
glob "^7.0.6"
istanbul-lib-coverage "^1.1.1"
- istanbul-lib-hook "^1.0.7"
- istanbul-lib-instrument "^1.7.4"
- istanbul-lib-report "^1.1.1"
- istanbul-lib-source-maps "^1.2.1"
- istanbul-reports "^1.1.1"
+ istanbul-lib-hook "^1.1.0"
+ istanbul-lib-instrument "^1.9.1"
+ istanbul-lib-report "^1.1.2"
+ istanbul-lib-source-maps "^1.2.2"
+ istanbul-reports "^1.1.3"
md5-hex "^1.2.0"
merge-source-map "^1.0.2"
micromatch "^2.3.11"
@@ -2845,10 +3052,10 @@ nyc@^11.1.0:
resolve-from "^2.0.0"
rimraf "^2.5.4"
signal-exit "^3.0.1"
- spawn-wrap "^1.3.8"
+ spawn-wrap "^1.4.2"
test-exclude "^4.1.1"
- yargs "^8.0.1"
- yargs-parser "^5.0.0"
+ yargs "^10.0.3"
+ yargs-parser "^8.0.0"
oauth-sign@~0.8.1:
version "0.8.2"
@@ -2858,6 +3065,14 @@ object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+object-inspect@~1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d"
+
+object-keys@^1.0.8:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
object-keys@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.2.0.tgz#cddec02998b091be42bf1035ae32e49f1cb6ea67"
@@ -2883,7 +3098,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-once@^1.3.0, once@^1.3.1, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -2960,16 +3175,26 @@ optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
+optjs@~3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee"
+
ordered-read-streams@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
dependencies:
readable-stream "^2.0.1"
-os-homedir@^1.0.1:
+os-homedir@^1.0.0, os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ dependencies:
+ lcid "^1.0.0"
+
os-locale@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
@@ -2978,10 +3203,17 @@ os-locale@^2.0.0:
lcid "^1.0.0"
mem "^1.1.0"
-os-tmpdir@~1.0.1:
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+osenv@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -3011,9 +3243,9 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
-parseurl@~1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
+parseurl@~1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
passport-jwt@^3.0.0:
version "3.0.0"
@@ -3081,12 +3313,6 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-path-type@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
- dependencies:
- pify "^2.0.0"
-
pathval@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
@@ -3119,9 +3345,9 @@ pkg-dir@^1.0.0:
dependencies:
find-up "^1.0.0"
-pluralize@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762"
+pluralize@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
prelude-ls@~1.1.2:
version "1.1.2"
@@ -3139,12 +3365,21 @@ progress@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
-proxy-addr@~1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918"
+protobufjs@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.2.tgz#59748d7dcf03d2db22c13da9feb024e16ab80c91"
dependencies:
- forwarded "~0.1.0"
- ipaddr.js "1.4.0"
+ ascli "~1"
+ bytebuffer "~5"
+ glob "^7.0.5"
+ yargs "^3.10.0"
+
+proxy-addr@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
+ dependencies:
+ forwarded "~0.1.2"
+ ipaddr.js "1.5.2"
pseudomap@^1.0.2:
version "1.0.2"
@@ -3157,6 +3392,13 @@ pump@^1.0.0, pump@^1.0.2:
end-of-stream "^1.1.0"
once "^1.3.1"
+pump@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.0.tgz#7946da1c8d622b098e2ceb2d3476582470829c9d"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
pumpify@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.3.5.tgz#1b671c619940abcaeac0ad0e3a3c164be760993b"
@@ -3177,11 +3419,11 @@ q@1.x.x:
version "1.5.0"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
-qs@6.4.0, qs@~6.4.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+qs@6.5.1:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
-qs@6.5.0, qs@^6.2.1:
+qs@^6.2.1:
version "6.5.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
@@ -3189,6 +3431,10 @@ qs@~6.3.0:
version "6.3.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
+qs@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
randomatic@^1.1.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
@@ -3200,14 +3446,24 @@ range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
-raw-body@~2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
+raw-body@2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
dependencies:
- bytes "2.4.0"
- iconv-lite "0.4.15"
+ bytes "3.0.0"
+ http-errors "1.6.2"
+ iconv-lite "0.4.19"
unpipe "1.0.0"
+rc@^1.1.7:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
+ dependencies:
+ deep-extend "~0.4.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
rc@~1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea"
@@ -3224,13 +3480,6 @@ read-pkg-up@^1.0.1:
find-up "^1.0.0"
read-pkg "^1.0.0"
-read-pkg-up@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
- dependencies:
- find-up "^2.0.0"
- read-pkg "^2.0.0"
-
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
@@ -3239,14 +3488,6 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
-read-pkg@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
- dependencies:
- load-json-file "^2.0.0"
- normalize-package-data "^2.3.2"
- path-type "^2.0.0"
-
readable-stream@2.2.7:
version "2.2.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.7.tgz#07057acbe2467b22042d36f98c5ad507054e95b1"
@@ -3259,7 +3500,7 @@ readable-stream@2.2.7:
string_decoder "~1.0.0"
util-deprecate "~1.0.1"
-"readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.0, readable-stream@^2.2.9, readable-stream@^2.3.3:
+"readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.0, readable-stream@^2.2.9, readable-stream@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
dependencies:
@@ -3312,6 +3553,10 @@ regenerator-runtime@^0.10.0:
version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
+regenerator-runtime@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
+
regex-cache@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
@@ -3319,7 +3564,7 @@ regex-cache@^0.4.2:
is-equal-shallow "^0.1.3"
is-primitive "^2.0.0"
-regexp-clone@0.0.1, regexp-clone@^0.0.1:
+regexp-clone@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589"
@@ -3459,6 +3704,12 @@ resolve@^1.0.0:
dependencies:
path-parse "^1.0.5"
+resolve@~1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
+ dependencies:
+ path-parse "^1.0.5"
+
restify-errors@^4.2.3:
version "4.3.0"
resolved "https://registry.yarnpkg.com/restify-errors/-/restify-errors-4.3.0.tgz#ec90f30934d7f3119135181dfc303e30be601abe"
@@ -3512,18 +3763,30 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"
+resumer@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759"
+ dependencies:
+ through "~2.3.4"
+
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
dependencies:
align-text "^0.1.1"
-rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.5.4, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
dependencies:
glob "^7.0.5"
+rimraf@^2.5.1, rimraf@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ dependencies:
+ glob "^7.0.5"
+
rimraf@~2.4.0:
version "2.4.5"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da"
@@ -3576,7 +3839,7 @@ rx-lite@*, rx-lite@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -3596,32 +3859,32 @@ semver@^5.0.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
-send@0.15.4:
- version "0.15.4"
- resolved "https://registry.yarnpkg.com/send/-/send-0.15.4.tgz#985faa3e284b0273c793364a35c6737bd93905b9"
+send@0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
dependencies:
- debug "2.6.8"
+ debug "2.6.9"
depd "~1.1.1"
destroy "~1.0.4"
encodeurl "~1.0.1"
escape-html "~1.0.3"
- etag "~1.8.0"
- fresh "0.5.0"
+ etag "~1.8.1"
+ fresh "0.5.2"
http-errors "~1.6.2"
- mime "1.3.4"
+ mime "1.4.1"
ms "2.0.0"
on-finished "~2.3.0"
range-parser "~1.2.0"
statuses "~1.3.1"
-serve-static@1.12.4:
- version "1.12.4"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.4.tgz#9b6aa98eeb7253c4eedc4c1f6fdbca609901a961"
+serve-static@1.13.1:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
dependencies:
encodeurl "~1.0.1"
escape-html "~1.0.3"
- parseurl "~1.3.1"
- send "0.15.4"
+ parseurl "~1.3.2"
+ send "0.16.1"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
@@ -3631,6 +3894,10 @@ setprototypeof@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -3686,6 +3953,12 @@ sntp@1.x.x:
dependencies:
hoek "2.x.x"
+source-map-support@^0.4.17:
+ version "0.4.18"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
+ dependencies:
+ source-map "^0.5.6"
+
"source-map@>= 0.1.2", source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
@@ -3696,16 +3969,16 @@ source-map@^0.4.4:
dependencies:
amdefine ">=0.0.4"
-spawn-wrap@^1.3.8:
- version "1.3.8"
- resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.8.tgz#fa2a79b990cbb0bb0018dca6748d88367b19ec31"
+spawn-wrap@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c"
dependencies:
foreground-child "^1.5.6"
mkdirp "^0.5.0"
os-homedir "^1.0.1"
- rimraf "^2.3.3"
+ rimraf "^2.6.2"
signal-exit "^3.0.2"
- which "^1.2.4"
+ which "^1.3.0"
spdx-correct@~1.0.0:
version "1.0.2"
@@ -3809,7 +4082,7 @@ string-width@^2.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
-string-width@^2.1.0:
+string-width@^2.1.0, string-width@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
dependencies:
@@ -3820,6 +4093,14 @@ string.prototype.codepointat@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78"
+string.prototype.trim@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.5.0"
+ function-bind "^1.0.2"
+
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@@ -3862,10 +4143,6 @@ strip-bom@^2.0.0:
dependencies:
is-utf8 "^0.2.0"
-strip-bom@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -3911,6 +4188,37 @@ taffydb@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
+tape@^4.6.3:
+ version "4.8.0"
+ resolved "https://registry.yarnpkg.com/tape/-/tape-4.8.0.tgz#f6a9fec41cc50a1de50fa33603ab580991f6068e"
+ dependencies:
+ deep-equal "~1.0.1"
+ defined "~1.0.0"
+ for-each "~0.3.2"
+ function-bind "~1.1.0"
+ glob "~7.1.2"
+ has "~1.0.1"
+ inherits "~2.0.3"
+ minimist "~1.2.0"
+ object-inspect "~1.3.0"
+ resolve "~1.4.0"
+ resumer "~0.0.0"
+ string.prototype.trim "~1.1.2"
+ through "~2.3.8"
+
+tar-pack@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
+ dependencies:
+ debug "^2.2.0"
+ fstream "^1.0.10"
+ fstream-ignore "^1.0.5"
+ once "^1.3.3"
+ readable-stream "^2.1.4"
+ rimraf "^2.5.1"
+ tar "^2.2.1"
+ uid-number "^0.0.6"
+
tar-pack@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae"
@@ -3924,7 +4232,7 @@ tar-pack@~3.3.0:
tar "~2.2.1"
uid-number "~0.0.6"
-tar@~2.2.1:
+tar@^2.2.1, tar@~2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
dependencies:
@@ -3960,7 +4268,7 @@ through2@^2.0.1, through2@^2.0.2, through2@~2.0.0:
readable-stream "^2.1.5"
xtend "~4.0.1"
-through@^2.3.6:
+through@^2.3.6, through@~2.3.4, through@~2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -4006,6 +4314,27 @@ tryit@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
+ttn@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/ttn/-/ttn-2.3.1.tgz#e32829e25aa39d397360a4135f4cef5a27f293a7"
+ dependencies:
+ babel-runtime "^6.26.0"
+ debug "^2.6.8"
+ google-protobuf "^3.3.0"
+ grpc "^1.4.1"
+ jsonwebtoken "^7.4.1"
+ mqtt "^2.15.0"
+ node-fetch "^1.7.3"
+ source-map-support "^0.4.17"
+ ttnapi "https://github.com/thethingsnetwork/api"
+
+"ttnapi@git+https://github.com/thethingsnetwork/api.git":
+ version "2.0.0"
+ resolved "git+https://github.com/thethingsnetwork/api.git#aa6e27d0a2c4548a92a917d063ef1a5ca4bd075d"
+ dependencies:
+ google-protobuf "^3.3.0"
+ grpc "^1.4.1"
+
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -4046,10 +4375,6 @@ type-detect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
-type-detect@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-3.0.0.tgz#46d0cc8553abb7b13a352b0d6dea2fd58f2d9b55"
-
type-detect@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea"
@@ -4078,7 +4403,7 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
-uid-number@~0.0.6:
+uid-number@^0.0.6, uid-number@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
@@ -4131,9 +4456,9 @@ util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-utils-merge@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
uuid@^3.0.0, uuid@^3.1.0:
version "3.1.0"
@@ -4146,9 +4471,9 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
-vary@~1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
vasync@^1.6.4:
version "1.6.4"
@@ -4201,12 +4526,18 @@ which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
-which@^1.0.5, which@^1.2.4, which@^1.2.9:
+which@^1.0.5, which@^1.2.9:
version "1.2.14"
resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
dependencies:
isexe "^2.0.0"
+which@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
+ dependencies:
+ isexe "^2.0.0"
+
wide-align@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
@@ -4217,6 +4548,10 @@ window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+window-size@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
+
wordwrap@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
@@ -4276,7 +4611,7 @@ xtend@~2.0.6:
is-object "~0.1.2"
object-keys "~0.2.0"
-y18n@^3.2.1:
+y18n@^3.2.0, y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
@@ -4284,35 +4619,40 @@ yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-yargs-parser@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
- dependencies:
- camelcase "^3.0.0"
-
-yargs-parser@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+yargs-parser@^8.0.0, yargs-parser@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
dependencies:
camelcase "^4.1.0"
-yargs@^8.0.1:
- version "8.0.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
+yargs@^10.0.3:
+ version "10.1.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.1.tgz#5fe1ea306985a099b33492001fa19a1e61efe285"
dependencies:
- camelcase "^4.1.0"
- cliui "^3.2.0"
+ cliui "^4.0.0"
decamelize "^1.1.1"
+ find-up "^2.1.0"
get-caller-file "^1.0.1"
os-locale "^2.0.0"
- read-pkg-up "^2.0.0"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^2.0.0"
which-module "^2.0.0"
y18n "^3.2.1"
- yargs-parser "^7.0.0"
+ yargs-parser "^8.1.0"
+
+yargs@^3.10.0:
+ version "3.32.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
+ dependencies:
+ camelcase "^2.0.1"
+ cliui "^3.0.3"
+ decamelize "^1.1.1"
+ os-locale "^1.4.0"
+ string-width "^1.0.1"
+ window-size "^0.1.4"
+ y18n "^3.2.0"
yargs@~3.10.0:
version "3.10.0"