<commit_before>'use strict';

var fs = require('fs');
var util = require('util');
var semver = require('semver');
var Error = require('./error');
var GHIssue = require('./github-ref-parser').Issue;

var config = {
    // first line should not start with \n, be 1 <= length
    // the optional body should be separated from title by exactly \n\n
    // only 1 \n is allowed at the end of the message.
    // capturing blocks: $1 - title, $2 - body
    // (\n\n between title and body and last \n, if any, are discarded)
    pattern: [/^([^\n]+)(?:\n\n([^\n][\S\s]*[^\n]))?\n?$/, Error.ERROR],
    capitalized: [true, Error.ERROR],
    invalidCharsInTitle: [/[^\w(), '"`\-:\./~\[\]*$={}]/, Error.ERROR],
    titlePreferredMaxLineLength: [50, Error.WARNING],
    titleMaxLineLength: [70, Error.ERROR],  // because GitHub adds an ellipsis after 70
    bodyMaxLineLength: [72, Error.WARNING],
    // specify the types/components that can prefix the commit message
    // the type  will be removed from the title before all the other validations
    // eg: perf: Speed up page load
    // eg: fix: i18n: Change login button text for spanish
    // Note that this is not an array as the others
    types: /^(fix|docs|feature|security|chore|refactor|test|config|perf|WIP):( [\w-]+:)? /,
    // validate GitHub issues location - should be placed in the last paragraph
    // of the body
    ghIssuesLocation: [true, Error.WARNING],
    // basic detection of imperative, present tense
    // http://regexr.com/3as25
    imperative: [new RegExp('^((fix|refactor|chang|updat|remov|releas|clos|' +
    'merg|rebas|mov|improv|includ|renam|us|finish|styl|hard.?cod|allow|' +
    'polish)(es|ed|ing))|((add|open|clean|correct|adjust|show|import|tweak' +
    ')(s|ed|ing))|(modified|modifies|modifying)|(stripped|strips|stripping)|' +
    '(makes|made|making)|(hides|hiding)|(misspelled|misspelling)|' +
    '(gets|got|getting)', 'i'), Error.WARNING]
};

/**
 * Commit message class
 *
 * @param {String} message The commit message
 * @constructor
 */
function CommitMessage(message) {
    this._errors = [];

    var cfg = this.config;
    var log = this._log.bind(this);

    if (!cfg.pattern[0].test(message)) {
        log('Commit message is not in the correct format, see\n' +
        'https://github.com/clns/node-commit-msg/blob/master/CONTRIBUTING.md#commit-message',
        cfg.pattern[1]);
        return; // can't continue past this point
    }

    var matches = message.match(cfg.pattern[0]);
    var t = this._title = matches[1];
    var b = this._body = matches[2];

    var isSemver = semver.valid(t);
    var index;
    var hasType = cfg.types.test(t);
    if (hasType) {
        t = t.replace(cfg.types, '');
    }
    var ghIssues;
    function getGHIssues() {
        if (ghIssues === undefined) {
            ghIssues = GHIssue.parse(message);
        }
        return ghIssues;
    }

    if (!isSemver && cfg.capitalized[0] && !/^[A-Z]/.test(t)) {
        log('Commit message should start with a capitalized letter',
        cfg.capitalized[1], [1, 1]);
    }

    if (t.length > cfg.titleMaxLineLength[0]) {
        log(util.format('First line (summary) should not exceed %d characters',
        cfg.titleMaxLineLength[0]), cfg.titleMaxLineLength[1],
        [1, cfg.titleMaxLineLength[0]]);
    } else if (t.length > cfg.titlePreferredMaxLineLength[0]) {
        log(util.format('First line (summary) should not exceed %d characters',
        cfg.titlePreferredMaxLineLength[0]), cfg.titlePreferredMaxLineLength[1],
        [1, cfg.titlePreferredMaxLineLength[0]]);
    }

    if ((index = t.search(/\s\s/)) !== -1) {
        log('First line (summary) contains invalid whitespace',
        Error.ERROR, [1, index+1]);
    }

    if (/[\.\s]$/.test(t)) {
        log('First line (summary) should not end with a period or whitespace',
        Error.ERROR, [1, t.length]);
    }

    if (cfg.invalidCharsInTitle[0] && (index = t.search(cfg.invalidCharsInTitle[0])) !== -1) {
        log('First line (summary) contains invalid characters',
        cfg.invalidCharsInTitle[1], [1, index+1]);
    }

    if (cfg.imperative[0] && (index = t.search(cfg.imperative[0])) !== -1) {
        log('Use imperative present tense, eg. "Fix bug" not ' +
        '"Fixed bug" or "Fixes bug". To get it right ask yourself: "If applied, ' +
        'this patch will <YOUR-COMMIT-MESSAGE-HERE>"', cfg.imperative[1], [1, index+1]);
    }

    if (cfg.ghIssuesLocation[0] && getGHIssues().length) {
        var firstIssue = ghIssues[ghIssues.length-1].toString();
        var totalParagraphs = message.match(/\n\n/g);
        var p = new RegExp(firstIssue + '.*\n\n');
        if (p.test(message)) {
            var lineIndex;
            message.split('\n').every(function(line, idx) {
                if ((index = line.indexOf(firstIssue)) !== -1) {
                    lineIndex = idx;
                    return false;
                }
                return true;
            });
            log('Issue references should be placed in the last paragraph of the body',
            cfg.ghIssuesLocation[1], [lineIndex+1, index+1]);
        }
    }

    if (b) {
        if (cfg.capitalized[0] && !/^[A-Z]/.test(b)) {
            log('Body should start with a capitalized letter',
            cfg.capitalized[1], [3, 1]);
        }
        var max;
        if (max = cfg.bodyMaxLineLength[0]) {
            var lines = b.split(/\n/);
            var longer = [];
            for (var i=0; i<lines.length; i++) {
                var line = lines[i];
                if (line.length > max) {
                    longer.push(i+1);
                }
            }
            var c;
            if (c = longer.length) {
                if (c <= 3) {
                    log(util.format('Lines %s in the commit body are ' +
                    'longer than %d characters. Body lines should ' +
                    'not exceed %d characters, except for compiler error ' +
                    'messages or other "non-prose" explanation'
                    , longer.join(', '), max, max), cfg.bodyMaxLineLength[1],
                    [longer[0]+2, max]);
                } else {
                    log(util.format('There are %d lines in the commit body ' +
                    'that are longer than %d characters. Body lines should ' +
                    'not exceed %d characters, except for compiler error ' +
                    'messages or other "non-prose" explanation'
                    , c, max, max), cfg.bodyMaxLineLength[1],
                    [longer[0]+2, max]);
                }
            }
        }
    }
}

Object.defineProperties(CommitMessage.prototype, {
    formattedMessages: {
        get: function() {
            var msgs = [];
            this._errors.forEach(function(e) { msgs.push(e.toFormattedString()); });
            return msgs.join( "\n" );
        }
    }
});

CommitMessage.prototype.config = config;

CommitMessage.prototype.hasErrors = function() {
    return !this._errors.every(function(e) { return !e.is(Error.ERROR); });
}

CommitMessage.prototype.hasWarnings = function() {
    return !this._errors.every(function(e) { return !e.is(Error.WARNING); });
}

CommitMessage.parseFromFile = function(file) {
    var msg = fs.readFileSync(file, { encoding: 'utf8' });
    return new CommitMessage(msg);
}

CommitMessage.parse = function(msg) {
    return new CommitMessage(msg);
}

// Helper method
CommitMessage.prototype._log = function(msg, type, location) {
    this._errors.push( new Error(msg, type, location) );
}

module.exports = CommitMessage;
<commit_msg>Add 'consolidate' to the non-imperative check<commit_after>'use strict';

var fs = require('fs');
var util = require('util');
var semver = require('semver');
var Error = require('./error');
var GHIssue = require('./github-ref-parser').Issue;

var config = {
    // first line should not start with \n, be 1 <= length
    // the optional body should be separated from title by exactly \n\n
    // only 1 \n is allowed at the end of the message.
    // capturing blocks: $1 - title, $2 - body
    // (\n\n between title and body and last \n, if any, are discarded)
    pattern: [/^([^\n]+)(?:\n\n([^\n][\S\s]*[^\n]))?\n?$/, Error.ERROR],
    capitalized: [true, Error.ERROR],
    invalidCharsInTitle: [/[^\w(), '"`\-:\./~\[\]*$={}]/, Error.ERROR],
    titlePreferredMaxLineLength: [50, Error.WARNING],
    titleMaxLineLength: [70, Error.ERROR],  // because GitHub adds an ellipsis after 70
    bodyMaxLineLength: [72, Error.WARNING],
    // specify the types/components that can prefix the commit message
    // the type  will be removed from the title before all the other validations
    // eg: perf: Speed up page load
    // eg: fix: i18n: Change login button text for spanish
    // Note that this is not an array as the others
    types: /^(fix|docs|feature|security|chore|refactor|test|config|perf|WIP):( [\w-]+:)? /,
    // validate GitHub issues location - should be placed in the last paragraph
    // of the body
    ghIssuesLocation: [true, Error.WARNING],
    // basic detection of imperative, present tense
    // http://regexr.com/3as25
    imperative: [new RegExp('^((fix|refactor|chang|updat|remov|releas|clos|' +
    'merg|rebas|mov|improv|includ|renam|us|finish|styl|hard.?cod|allow|' +
    'polish|consolidat' +
    ')(es|ed|ing))|((add|open|clean|correct|adjust|show|import|tweak' +
    ')(s|ed|ing))|(modified|modifies|modifying)|(stripped|strips|stripping)|' +
    '(makes|made|making)|(hides|hiding)|(misspelled|misspelling)|' +
    '(gets|got|getting)', 'i'), Error.WARNING]
};

/**
 * Commit message class
 *
 * @param {String} message The commit message
 * @constructor
 */
function CommitMessage(message) {
    this._errors = [];

    var cfg = this.config;
    var log = this._log.bind(this);

    if (!cfg.pattern[0].test(message)) {
        log('Commit message is not in the correct format, see\n' +
        'https://github.com/clns/node-commit-msg/blob/master/CONTRIBUTING.md#commit-message',
        cfg.pattern[1]);
        return; // can't continue past this point
    }

    var matches = message.match(cfg.pattern[0]);
    var t = this._title = matches[1];
    var b = this._body = matches[2];

    var isSemver = semver.valid(t);
    var index;
    var hasType = cfg.types.test(t);
    if (hasType) {
        t = t.replace(cfg.types, '');
    }
    var ghIssues;
    function getGHIssues() {
        if (ghIssues === undefined) {
            ghIssues = GHIssue.parse(message);
        }
        return ghIssues;
    }

    if (!isSemver && cfg.capitalized[0] && !/^[A-Z]/.test(t)) {
        log('Commit message should start with a capitalized letter',
        cfg.capitalized[1], [1, 1]);
    }

    if (t.length > cfg.titleMaxLineLength[0]) {
        log(util.format('First line (summary) should not exceed %d characters',
        cfg.titleMaxLineLength[0]), cfg.titleMaxLineLength[1],
        [1, cfg.titleMaxLineLength[0]]);
    } else if (t.length > cfg.titlePreferredMaxLineLength[0]) {
        log(util.format('First line (summary) should not exceed %d characters',
        cfg.titlePreferredMaxLineLength[0]), cfg.titlePreferredMaxLineLength[1],
        [1, cfg.titlePreferredMaxLineLength[0]]);
    }

    if ((index = t.search(/\s\s/)) !== -1) {
        log('First line (summary) contains invalid whitespace',
        Error.ERROR, [1, index+1]);
    }

    if (/[\.\s]$/.test(t)) {
        log('First line (summary) should not end with a period or whitespace',
        Error.ERROR, [1, t.length]);
    }

    if (cfg.invalidCharsInTitle[0] && (index = t.search(cfg.invalidCharsInTitle[0])) !== -1) {
        log('First line (summary) contains invalid characters',
        cfg.invalidCharsInTitle[1], [1, index+1]);
    }

    if (cfg.imperative[0] && (index = t.search(cfg.imperative[0])) !== -1) {
        log('Use imperative present tense, eg. "Fix bug" not ' +
        '"Fixed bug" or "Fixes bug". To get it right ask yourself: "If applied, ' +
        'this patch will <YOUR-COMMIT-MESSAGE-HERE>"', cfg.imperative[1], [1, index+1]);
    }

    if (cfg.ghIssuesLocation[0] && getGHIssues().length) {
        var firstIssue = ghIssues[ghIssues.length-1].toString();
        var totalParagraphs = message.match(/\n\n/g);
        var p = new RegExp(firstIssue + '.*\n\n');
        if (p.test(message)) {
            var lineIndex;
            message.split('\n').every(function(line, idx) {
                if ((index = line.indexOf(firstIssue)) !== -1) {
                    lineIndex = idx;
                    return false;
                }
                return true;
            });
            log('Issue references should be placed in the last paragraph of the body',
            cfg.ghIssuesLocation[1], [lineIndex+1, index+1]);
        }
    }

    if (b) {
        if (cfg.capitalized[0] && !/^[A-Z]/.test(b)) {
            log('Body should start with a capitalized letter',
            cfg.capitalized[1], [3, 1]);
        }
        var max;
        if (max = cfg.bodyMaxLineLength[0]) {
            var lines = b.split(/\n/);
            var longer = [];
            for (var i=0; i<lines.length; i++) {
                var line = lines[i];
                if (line.length > max) {
                    longer.push(i+1);
                }
            }
            var c;
            if (c = longer.length) {
                if (c <= 3) {
                    log(util.format('Lines %s in the commit body are ' +
                    'longer than %d characters. Body lines should ' +
                    'not exceed %d characters, except for compiler error ' +
                    'messages or other "non-prose" explanation'
                    , longer.join(', '), max, max), cfg.bodyMaxLineLength[1],
                    [longer[0]+2, max]);
                } else {
                    log(util.format('There are %d lines in the commit body ' +
                    'that are longer than %d characters. Body lines should ' +
                    'not exceed %d characters, except for compiler error ' +
                    'messages or other "non-prose" explanation'
                    , c, max, max), cfg.bodyMaxLineLength[1],
                    [longer[0]+2, max]);
                }
            }
        }
    }
}

Object.defineProperties(CommitMessage.prototype, {
    formattedMessages: {
        get: function() {
            var msgs = [];
            this._errors.forEach(function(e) { msgs.push(e.toFormattedString()); });
            return msgs.join( "\n" );
        }
    }
});

CommitMessage.prototype.config = config;

CommitMessage.prototype.hasErrors = function() {
    return !this._errors.every(function(e) { return !e.is(Error.ERROR); });
}

CommitMessage.prototype.hasWarnings = function() {
    return !this._errors.every(function(e) { return !e.is(Error.WARNING); });
}

CommitMessage.parseFromFile = function(file) {
    var msg = fs.readFileSync(file, { encoding: 'utf8' });
    return new CommitMessage(msg);
}

CommitMessage.parse = function(msg) {
    return new CommitMessage(msg);
}

// Helper method
CommitMessage.prototype._log = function(msg, type, location) {
    this._errors.push( new Error(msg, type, location) );
}

module.exports = CommitMessage;
<|endoftext|>


<commit_before>import _ from 'lodash'
import { join, resolve } from 'path'

const Options = {}

export default Options

Options.from = function (_options) {
  const options = Object.assign({}, _options)

  _.defaultsDeep(options, Options.defaults)

  // Resolve dirs
  const hasValue = (v) => v && typeof v === 'string'
  options.rootDir = hasValue(options.rootDir) ? resolve(options.rootDir) : process.cwd()
  options.srcDir = hasValue(options.srcDir) ? join(options.rootDir, options.srcDir) : options.rootDir

  // Computed dirs
  options.apiDir = join(options.srcDir, options.apiDirname)
  options.controllerDir = join(options.srcDir, options.controllerDirname)
  options.buildDir = join(options.rootDir, options.buildDirname)
  options.modulesDir = join(options.rootDir, options.modulesDirname)

  return options
}

Options.defaults = {
  dev: process.env.NODE_ENV !== 'production',
  buildDirname: '.dynapi',
  apiDirname: 'api',
  controllerDirname: 'controller',
  modulesDirname: 'node_modules',
  responseTimeout: 800,
  methods: [
    'checkout',
    'copy',
    'delete',
    'get',
    'head',
    'lock',
    'merge',
    'mkactivity',
    'mkcol',
    'move',
    'msearch',
    'notify',
    'options',
    'patch',
    'post',
    'purge',
    'put',
    'report',
    'search',
    'subscribe',
    'trace',
    'unlock',
    'unsubscribe',
    'all'
  ]
}
<commit_msg>[Common] Update `options.js`<commit_after>import _ from 'lodash'
import { join, resolve } from 'path'

const Options = {}

export default Options

Options.from = function (_options) {
  const options = Object.assign({}, _options)

  _.defaultsDeep(options, Options.defaults)

  // Resolve dirs
  const hasValue = (v) => v && typeof v === 'string'
  options.rootDir = hasValue(options.rootDir) ? resolve(options.rootDir) : process.cwd()
  options.srcDir = hasValue(options.srcDir) ? join(options.rootDir, options.srcDir) : options.rootDir

  // Computed dirs
  options.apiDir = join(options.srcDir, options.apiDirname)
  options.controllerDir = join(options.srcDir, options.controllerDirname)
  options.modulesDir = join(options.rootDir, options.modulesDirname)

  // Internal dirs
  options.buildDir = join(options.rootDir, options.buildDirname)
  options.buildApiDir = join(options.buildDir, 'api')
  options.buildControllerDir = join(options.buildDir, 'controller')

  return options
}

Options.defaults = {
  dev: process.env.NODE_ENV !== 'production',
  buildDirname: '.dynapi',
  apiDirname: 'api',
  controllerDirname: 'controller',
  modulesDirname: 'node_modules',
  responseTimeout: 800,
  methods: [
    'checkout',
    'copy',
    'delete',
    'get',
    'head',
    'lock',
    'merge',
    'mkactivity',
    'mkcol',
    'move',
    'msearch',
    'notify',
    'options',
    'patch',
    'post',
    'purge',
    'put',
    'report',
    'search',
    'subscribe',
    'trace',
    'unlock',
    'unsubscribe',
    'all'
  ]
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-ul
 *
 * iotagent-ul is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-ul is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with iotagent-ul.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[iot_support@tid.es]
 *
 * Modified by: Fernando Méndez, Daniel Calvo - ATOS Research & Innovation
 */

/* eslint-disable no-prototype-builtins */

const iotAgentLib = require('iotagent-node-lib');
const regenerateTransid = iotAgentLib.regenerateTransid;
const config = require('./configService');
const transportSelector = require('./transportSelector');
const intoTrans = iotAgentLib.intoTrans;
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
const fillService = iotAgentLib.fillService;
const _ = require('underscore');
const utils = require('./iotaUtils');
const async = require('async');
const ulParser = require('./ulParser');
const constants = require('./constants');
let context = {
    op: 'IOTAUL.Common.Binding'
};

/**
 * Find the attribute given by its name between all the active attributes of the given device, returning its type, or
 * null otherwise.
 *
 * @param {String} attribute        Name of the attribute to find.
 * @param {Object} device           Device object containing all the information about a device.
 * @return {String}                 String identifier of the attribute type.
 */
function guessType(attribute, device) {
    for (let i = 0; i < device.active.length; i++) {
        if (device.active[i].name === attribute) {
            return device.active[i].type;
        }
    }

    if (attribute === constants.TIMESTAMP_ATTRIBUTE) {
        return constants.TIMESTAMP_TYPE_NGSI2;
    }
    return constants.DEFAULT_ATTRIBUTE_TYPE;
}

function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) {
    transportSelector.applyFunctionFromBinding(
        [apiKey, deviceId, results],
        'sendConfigurationToDevice',
        device.transport,
        callback
    );
}

/**
 * Deals with configuration requests coming from the device. Whenever a new configuration requests arrives with a list
 * of attributes to retrieve, this handler asks the Context Broker for the values of those attributes, and publish a
 * new message in the "/1234/MQTT_2/configuration/values" topic
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} deviceId         Id of the device to be updated.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Object} objMessage          JSON object received.
 */
function manageConfigurationRequest(apiKey, deviceId, device, objMessage) {
    utils.manageConfiguration(
        apiKey,
        deviceId,
        device,
        objMessage,
        async.apply(sendConfigurationToDevice, device),
        function (error) {
            if (error) {
                iotAgentLib.alarms.raise(constants.MQTTB_ALARM, error);
            } else {
                iotAgentLib.alarms.release(constants.MQTTB_ALARM);
                config
                    .getLogger()
                    .debug(context, 'Configuration request finished for APIKey [%s] and Device [%s]', apiKey, deviceId);
            }
            finishSouthBoundTransaction(null);
        }
    );
}

/**
 * Utility function used to reduce UL Payload group arrays and process them to update their value in the Context Broker.
 * This function process a new element of the payload group, generating a function ready to be called with a callback
 * in a async.series() call.
 *
 * @param {Object} device       Object containing all the information about the device sending the measures.
 * @param {String} apikey       APIKey of the service the device belongs to.
 * @param {Array} previous      Array of prepared functions that send information to the Context Broker.
 * @param {Object} current      Information sent by the device.
 * @param {Number} index        Index of the group in the array.
 * @return {Array}             Updated array of functions.
 */

/* eslint-disable-next-line no-unused-vars */
function processMeasureGroup(device, apikey, previous, current, index) {
    const values = [];
    if (current.command) {
        previous.push(
            iotAgentLib.setCommandResult.bind(
                null,
                device.name,
                config.getConfig().iota.defaultResource,
                apikey,
                current.command,
                current.value,
                constants.COMMAND_STATUS_COMPLETED,
                device
            )
        );
    } else {
        for (const k in current) {
            if (current.hasOwnProperty(k)) {
                values.push({
                    name: k,
                    type: guessType(k, device),
                    value: current[k]
                });
            }
        }

        previous.push(iotAgentLib.update.bind(null, device.name, device.type, '', values, device));
    }

    return previous;
}

/**
 * Adds multiple measures to the Context Broker. Multiple measures come in the form of single-level JSON objects,
 * whose keys are the attribute names and whose values are the attribute values.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {String} messageStr       UL payload parsed to string.
 * @param {String} message          UL payload.
 */
function multipleMeasures(apiKey, device, messageStr, message) {
    let updates = [];
    let parsedMessage;

    config.getLogger().debug('Processing multiple measures for device [%s] with apiKey [%s]', device.id, apiKey);

    // Parse Message
    try {
        parsedMessage = ulParser.parse(messageStr);
    } catch (e) {
        config.getLogger().error(context, 'MEASURES-003: Parse error parsing incoming message [%]', messageStr);
        return;
    }
    config.getLogger().debug(context, 'stringMessage: [%s] parsedMessage: [%s]', messageStr, parsedMessage);
    updates = parsedMessage.reduce(processMeasureGroup.bind(null, device, apiKey), []);

    async.series(updates, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().info(
                context,
                'Multiple measures for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Adds a single measure to the context broker. The message for single measures contains the direct value to
 * be inserted in the attribute, given by its name.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} attribute        Name of the attribute to update.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Buffer} messageStr       Raw message coming from the client parsed to string.
 * @param {Buffer} message          Raw message coming from the client.
 */
function singleMeasure(apiKey, attribute, device, messageStr, message) {
    config
        .getLogger()
        .debug('Processing single measure [%s] for device [%s] with apiKey [%s]', messageStr, device.id, apiKey);
    try {
        JSON.parse(messageStr); // just check it
    } catch (e) {
        messageStr = message.toString('hex');
    }
    const values = [
        {
            name: attribute,
            type: guessType(attribute, device),
            value: messageStr
        }
    ];

    iotAgentLib.update(device.name, device.type, '', values, device, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().debug(
                context,
                'Single measure for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Handles an incoming message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      message body (Object or Buffer, depending on the value).
 */
function messageHandler(topic, message, protocol) {
    if (topic[0] !== '/') {
        topic = '/' + topic;
    }
    const topicInformation = topic.split('/');
    let parsedMessage;

    if (topicInformation[1].toLowerCase() === 'ul') {
        topicInformation.splice(1, 1);
    }
    const apiKey = topicInformation[1];
    const deviceId = topicInformation[2];
    const messageStr = message.toString();

    function processMessageForDevice(device, apiKey, topicInformation) {
        iotAgentLib.alarms.release(constants.MQTTB_ALARM);

        if (
            topicInformation[3] === constants.CONFIGURATION_SUFIX &&
            topicInformation[4] === constants.CONFIGURATION_COMMAND_SUFIX &&
            message
        ) {
            parsedMessage = ulParser.parseConfigurationRequest(messageStr);
            manageConfigurationRequest(apiKey, deviceId, device, parsedMessage);
        } else if (topicInformation[3] === constants.CONFIGURATION_COMMAND_UPDATE) {
            const commandObj = ulParser.result(messageStr);
            utils.updateCommand(
                apiKey,
                device,
                commandObj.result,
                commandObj.command,
                constants.COMMAND_STATUS_COMPLETED,
                function (error) {
                    config.getLogger().debug('Command updated with result: %s', error);
                }
            );
        } else if (topicInformation[4]) {
            singleMeasure(apiKey, topicInformation[4], device, messageStr, message);
        } else if (topicInformation[3] === constants.MEASURES_SUFIX) {
            multipleMeasures(apiKey, device, messageStr, message);
        } else {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                "MEASURES-004: Couldn't process message [%s] due to format issues.",
                /*jshint quotmark: single */
                message
            );
        }
    }

    function processDeviceMeasure(error, device) {
        if (error) {
            context = fillService(context, { service: 'n/a', subservice: 'n/a' });
            config.getLogger().warn(context, 'MEASURES-004: Device not found for topic [%s]', topic);
        } else {
            const localContext = _.clone(context);

            localContext.service = device.service;
            localContext.subservice = device.subservice;

            intoTrans(localContext, processMessageForDevice)(device, apiKey, topicInformation);
        }
    }

    utils.retrieveDevice(deviceId, apiKey, protocol, processDeviceMeasure);
}

/**
 * Handles an incoming AMQP message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the AMQP topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      AMQP message body (Object or Buffer, depending on the value).
 */
function amqpMessageHandler(topic, message) {
    regenerateTransid(topic);
    messageHandler(topic, message, 'AMQP');
}

/**
 * Handles an incoming MQTT message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the MQTT topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      MQTT message body (Object or Buffer, depending on the value).
 */
function mqttMessageHandler(topic, message) {
    regenerateTransid(topic);
    config.getLogger().debug(context, 'message topic: %s', topic);
    messageHandler(topic, message, 'MQTT');
}

exports.amqpMessageHandler = amqpMessageHandler;
exports.mqttMessageHandler = mqttMessageHandler;
exports.messageHandler = messageHandler;
exports.processMeasureGroup = processMeasureGroup;
exports.guessType = guessType;
<commit_msg>remove unused arg<commit_after>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-ul
 *
 * iotagent-ul is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-ul is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with iotagent-ul.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[iot_support@tid.es]
 *
 * Modified by: Fernando Méndez, Daniel Calvo - ATOS Research & Innovation
 */

/* eslint-disable no-prototype-builtins */

const iotAgentLib = require('iotagent-node-lib');
const regenerateTransid = iotAgentLib.regenerateTransid;
const config = require('./configService');
const transportSelector = require('./transportSelector');
const intoTrans = iotAgentLib.intoTrans;
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
const fillService = iotAgentLib.fillService;
const _ = require('underscore');
const utils = require('./iotaUtils');
const async = require('async');
const ulParser = require('./ulParser');
const constants = require('./constants');
let context = {
    op: 'IOTAUL.Common.Binding'
};

/**
 * Find the attribute given by its name between all the active attributes of the given device, returning its type, or
 * null otherwise.
 *
 * @param {String} attribute        Name of the attribute to find.
 * @param {Object} device           Device object containing all the information about a device.
 * @return {String}                 String identifier of the attribute type.
 */
function guessType(attribute, device) {
    for (let i = 0; i < device.active.length; i++) {
        if (device.active[i].name === attribute) {
            return device.active[i].type;
        }
    }

    if (attribute === constants.TIMESTAMP_ATTRIBUTE) {
        return constants.TIMESTAMP_TYPE_NGSI2;
    }
    return constants.DEFAULT_ATTRIBUTE_TYPE;
}

function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) {
    transportSelector.applyFunctionFromBinding(
        [apiKey, deviceId, results],
        'sendConfigurationToDevice',
        device.transport,
        callback
    );
}

/**
 * Deals with configuration requests coming from the device. Whenever a new configuration requests arrives with a list
 * of attributes to retrieve, this handler asks the Context Broker for the values of those attributes, and publish a
 * new message in the "/1234/MQTT_2/configuration/values" topic
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} deviceId         Id of the device to be updated.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Object} objMessage          JSON object received.
 */
function manageConfigurationRequest(apiKey, deviceId, device, objMessage) {
    utils.manageConfiguration(
        apiKey,
        deviceId,
        device,
        objMessage,
        async.apply(sendConfigurationToDevice, device),
        function (error) {
            if (error) {
                iotAgentLib.alarms.raise(constants.MQTTB_ALARM, error);
            } else {
                iotAgentLib.alarms.release(constants.MQTTB_ALARM);
                config
                    .getLogger()
                    .debug(context, 'Configuration request finished for APIKey [%s] and Device [%s]', apiKey, deviceId);
            }
            finishSouthBoundTransaction(null);
        }
    );
}

/**
 * Utility function used to reduce UL Payload group arrays and process them to update their value in the Context Broker.
 * This function process a new element of the payload group, generating a function ready to be called with a callback
 * in a async.series() call.
 *
 * @param {Object} device       Object containing all the information about the device sending the measures.
 * @param {String} apikey       APIKey of the service the device belongs to.
 * @param {Array} previous      Array of prepared functions that send information to the Context Broker.
 * @param {Object} current      Information sent by the device.
 * @param {Number} index        Index of the group in the array.
 * @return {Array}             Updated array of functions.
 */

/* eslint-disable-next-line no-unused-vars */
function processMeasureGroup(device, apikey, previous, current, index) {
    const values = [];
    if (current.command) {
        previous.push(
            iotAgentLib.setCommandResult.bind(
                null,
                device.name,
                config.getConfig().iota.defaultResource,
                apikey,
                current.command,
                current.value,
                constants.COMMAND_STATUS_COMPLETED,
                device
            )
        );
    } else {
        for (const k in current) {
            if (current.hasOwnProperty(k)) {
                values.push({
                    name: k,
                    type: guessType(k, device),
                    value: current[k]
                });
            }
        }

        previous.push(iotAgentLib.update.bind(null, device.name, device.type, '', values, device));
    }

    return previous;
}

/**
 * Adds multiple measures to the Context Broker. Multiple measures come in the form of single-level JSON objects,
 * whose keys are the attribute names and whose values are the attribute values.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {String} messageStr       UL payload parsed to string.
 */
function multipleMeasures(apiKey, device, messageStr) {
    let updates = [];
    let parsedMessage;

    config.getLogger().debug('Processing multiple measures for device [%s] with apiKey [%s]', device.id, apiKey);

    // Parse Message
    try {
        parsedMessage = ulParser.parse(messageStr);
    } catch (e) {
        config.getLogger().error(context, 'MEASURES-003: Parse error parsing incoming message [%]', messageStr);
        return;
    }
    config.getLogger().debug(context, 'stringMessage: [%s] parsedMessage: [%s]', messageStr, parsedMessage);
    updates = parsedMessage.reduce(processMeasureGroup.bind(null, device, apiKey), []);

    async.series(updates, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().info(
                context,
                'Multiple measures for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Adds a single measure to the context broker. The message for single measures contains the direct value to
 * be inserted in the attribute, given by its name.
 *
 * @param {String} apiKey           API Key corresponding to the Devices configuration.
 * @param {String} attribute        Name of the attribute to update.
 * @param {Object} device           Device object containing all the information about a device.
 * @param {Buffer} messageStr       Raw message coming from the client parsed to string.
 * @param {Buffer} message          Raw message coming from the client.
 */
function singleMeasure(apiKey, attribute, device, messageStr, message) {
    config
        .getLogger()
        .debug('Processing single measure [%s] for device [%s] with apiKey [%s]', messageStr, device.id, apiKey);
    try {
        JSON.parse(messageStr); // just check it
    } catch (e) {
        messageStr = message.toString('hex');
    }
    const values = [
        {
            name: attribute,
            type: guessType(attribute, device),
            value: messageStr
        }
    ];

    iotAgentLib.update(device.name, device.type, '', values, device, function (error) {
        if (error) {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                " MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
                /*jshint quotmark: single */
                error
            );
        } else {
            // prettier-ignore
            config.getLogger().debug(
                context,
                'Single measure for device [%s] with apiKey [%s] successfully updated',
                device.id,
                apiKey
            );
        }
        finishSouthBoundTransaction(null);
    });
}

/**
 * Handles an incoming message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      message body (Object or Buffer, depending on the value).
 */
function messageHandler(topic, message, protocol) {
    if (topic[0] !== '/') {
        topic = '/' + topic;
    }
    const topicInformation = topic.split('/');
    let parsedMessage;

    if (topicInformation[1].toLowerCase() === 'ul') {
        topicInformation.splice(1, 1);
    }
    const apiKey = topicInformation[1];
    const deviceId = topicInformation[2];
    const messageStr = message.toString();

    function processMessageForDevice(device, apiKey, topicInformation) {
        iotAgentLib.alarms.release(constants.MQTTB_ALARM);

        if (
            topicInformation[3] === constants.CONFIGURATION_SUFIX &&
            topicInformation[4] === constants.CONFIGURATION_COMMAND_SUFIX &&
            message
        ) {
            parsedMessage = ulParser.parseConfigurationRequest(messageStr);
            manageConfigurationRequest(apiKey, deviceId, device, parsedMessage);
        } else if (topicInformation[3] === constants.CONFIGURATION_COMMAND_UPDATE) {
            const commandObj = ulParser.result(messageStr);
            utils.updateCommand(
                apiKey,
                device,
                commandObj.result,
                commandObj.command,
                constants.COMMAND_STATUS_COMPLETED,
                function (error) {
                    config.getLogger().debug('Command updated with result: %s', error);
                }
            );
        } else if (topicInformation[4]) {
            singleMeasure(apiKey, topicInformation[4], device, messageStr, message);
        } else if (topicInformation[3] === constants.MEASURES_SUFIX) {
            multipleMeasures(apiKey, device, messageStr);
        } else {
            config.getLogger().error(
                context,
                /*jshint quotmark: double */
                "MEASURES-004: Couldn't process message [%s] due to format issues.",
                /*jshint quotmark: single */
                message
            );
        }
    }

    function processDeviceMeasure(error, device) {
        if (error) {
            context = fillService(context, { service: 'n/a', subservice: 'n/a' });
            config.getLogger().warn(context, 'MEASURES-004: Device not found for topic [%s]', topic);
        } else {
            const localContext = _.clone(context);

            localContext.service = device.service;
            localContext.subservice = device.subservice;

            intoTrans(localContext, processMessageForDevice)(device, apiKey, topicInformation);
        }
    }

    utils.retrieveDevice(deviceId, apiKey, protocol, processDeviceMeasure);
}

/**
 * Handles an incoming AMQP message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the AMQP topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      AMQP message body (Object or Buffer, depending on the value).
 */
function amqpMessageHandler(topic, message) {
    regenerateTransid(topic);
    messageHandler(topic, message, 'AMQP');
}

/**
 * Handles an incoming MQTT message, extracting the API Key, device Id and attribute to update (in the case of single
 * measures) from the MQTT topic.
 *
 * @param {String} topic        Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
 * @param {Object} message      MQTT message body (Object or Buffer, depending on the value).
 */
function mqttMessageHandler(topic, message) {
    regenerateTransid(topic);
    config.getLogger().debug(context, 'message topic: %s', topic);
    messageHandler(topic, message, 'MQTT');
}

exports.amqpMessageHandler = amqpMessageHandler;
exports.mqttMessageHandler = mqttMessageHandler;
exports.messageHandler = messageHandler;
exports.processMeasureGroup = processMeasureGroup;
exports.guessType = guessType;
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

const Q = require('q-lite'),
      Areq = require('areq'),
      CCZnp = require('cc-znp'),
      ZSC = CCZnp.constants,
      util = require('util'),
      EventEmitter = require('events').EventEmitter;

const zdoHelper = require('./zdo_helper'),
      ZigbeeError = require('../errors/zigbee_error');

const requestFn = {}

function Zdo(controller) {
    EventEmitter.call(this);
    this.setMaxListeners(40)
    this._controller = controller;
    this._areq = new Areq(controller, 70000);
}

util.inherits(Zdo, EventEmitter);

/*************************************************************************************************/
/*** Public APIs                                                                               ***/
/*************************************************************************************************/
Zdo.prototype.request = async function (apiName, valObj) {
    var requestType = zdoHelper.getRequestType(apiName);

    var handler = requestFn[requestType]
    if(!handler) {
        throw new Error('Unknown request type.')
    }
    return await handler(this, apiName, valObj)
};

/*************************************************************************************************/
/*** Protected Methods                                                                         ***/
/*************************************************************************************************/
Zdo.prototype._sendZdoRequestViaZnp = async function (apiName, valObj) {
    const rsp = await this._controller._znp.zdoRequest(apiName, valObj)
    if(!rsp){
        throw new Error(`Unable to execute ${apiName}`)
    }
    if (apiName !== 'startupFromApp' && rsp.status)
        throw new ZigbeeError('request unsuccess: ' + rsp.status, rsp.status)
    return rsp
};

requestFn.rspless = function (self, apiName, valObj) {
    return self._sendZdoRequestViaZnp(apiName, valObj);
};

requestFn.generic = function (self, apiName, valObj) {
    var deferred = Q.defer(),
        areq = self._areq,
        areqEvtKey = zdoHelper.generateEventOfRequest(apiName, valObj),
        abortKey

    function handleAbort(){
        deferred.reject("__abort__")
    }

    if (areqEvtKey) {
        abortKey = zdoHelper.generateDstAddrOfRequest(valObj)
        if(abortKey) self.once(abortKey, handleAbort)
        areq.register(areqEvtKey, deferred, function (payload) {
            areq.resolve(areqEvtKey, payload);
        });
    }

    self._sendZdoRequestViaZnp(apiName, valObj, function (err, rsp) {
        if (err)
            areq.reject(areqEvtKey, err);
    })

    deferred.promise.catch(()=>{}).then(()=>{
        if(abortKey) self.removeListener(abortKey, handleAbort)
    })

    return deferred.promise
};

requestFn.special = function (self, apiName, valObj) {
    if (apiName === 'serverDiscReq') {
        // broadcast, remote device may not response when no bits match in mask
        // listener at controller.on('ZDO:serverDiscRsp')
        return requestFn.rspless(self, 'serverDiscReq', valObj);
    } else if (apiName === 'bindReq') {
        if (valObj.dstaddrmode === ZSC.AF.addressMode.ADDR_16BIT)
            throw new Error('TI not support address 16bit mode.');
        else
            return requestFn.rspless(self, 'bindReq', valObj);
    } else if (apiName === 'mgmtPermitJoinReq') {
        if (valObj.dstaddr === 0xFFFC)  // broadcast to all routers (and coord), no waiting for AREQ rsp
            return requestFn.rspless(self, 'mgmtPermitJoinReq', valObj);
        else
            return requestFn.generic(self, 'mgmtPermitJoinReq', valObj);
    } else {
        throw new Error('No such request.')
    }
};

requestFn.concat = function (self, apiName, valObj) {
    if (apiName === 'nwkAddrReq' || apiName === 'ieeeAddrReq')
        return requestFn._concatAddr(self, apiName, valObj);
    else if (apiName === 'mgmtNwkDiscReq')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'networkcount',
            listcount: 'networklistcount',
            list: 'networklist'
        });
    else if (apiName === 'mgmtLqiReq')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'neighbortableentries',
            listcount: 'neighborlqilistcount',
            list: 'neighborlqilist'
        });
    else if (apiName === 'mgmtRtgReq')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'routingtableentries',
            listcount: 'routingtablelistcount',
            list: 'routingtablelist'
        });
    else if (apiName === 'mgmtBindRsp')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'bindingtableentries',
            listcount: 'bindingtablelistcount',
            list: 'bindingtablelist'
        });
    else
        throw new Error('No such request.');
};

requestFn._concatAddr = function (self, apiName, valObj) {
    var totalToGet = null,
        accum = 0,
        nextIndex = valObj.startindex,
        reqObj = {
            reqtype: valObj.reqtype,
            startindex: valObj.startindex    // start from 0
        },
        finalRsp = {
            status: null,
            ieeeaddr: null,
            nwkaddr: null,
            startindex: valObj.startindex,
            numassocdev: null,
            assocdevlist: []
        };

    if (apiName === 'nwkAddrReq')
        reqObj.ieeeaddr = valObj.ieeeaddr;
    else
        reqObj.shortaddr = valObj.shortaddr;

    async function recursiveRequest() {
        const rsp = await requestFn.generic(self, apiName, reqObj)
        if (rsp.status) {
            throw new ZigbeeError('request unsuccess: ' + rsp.status, rsp.status)
        }
        finalRsp.status = rsp.status;
        finalRsp.ieeeaddr = finalRsp.ieeeaddr || rsp.ieeeaddr;
        finalRsp.nwkaddr = finalRsp.nwkaddr || rsp.nwkaddr;
        finalRsp.numassocdev = finalRsp.numassocdev || rsp.numassocdev;
        finalRsp.assocdevlist = finalRsp.assocdevlist.concat(rsp.assocdevlist);

        totalToGet = totalToGet || (finalRsp.numassocdev - finalRsp.startindex);    // compute at 1st rsp back
        accum = accum + rsp.assocdevlist.length;

        if (valObj.reqtype === 1 && accum < totalToGet) {  // extended, include associated devices
            nextIndex = nextIndex + rsp.assocdevlist.length;
            reqObj.startindex = nextIndex;
            return await recursiveRequest();
        }
        return finalRsp
    };

    return recursiveRequest();
};

requestFn._concatList = function (self, apiName, valObj, listKeys) {
    // valObj = { dstaddr[, scanchannels, scanduration], startindex }
    // listKeys = { entries: 'networkcount', listcount: 'networklistcount', list: 'networklist' };
    var totalToGet = null,
        accum = 0,
        nextIndex = valObj.startindex,
        reqObj = {
            dstaddr: valObj.dstaddr,
            scanchannels: valObj.scanchannels,
            scanduration: valObj.scanduration,
            startindex: valObj.startindex    // starts from 0
        },
        finalRsp = {
            srcaddr: null,
            status: null,
            startindex: valObj.startindex
        };

    finalRsp[listKeys.entries] = null;       // finalRsp.networkcount = null
    finalRsp[listKeys.listcount] = null;     // finalRsp.networklistcount = null
    finalRsp[listKeys.list] = [];            // finalRsp.networklist = []

    if (apiName === 'mgmtNwkDiscReq') {
        reqObj.scanchannels = valObj.scanchannels;
        reqObj.scanduration = valObj.scanduration;
    }

    async function recursiveRequest () {
        const rsp = await requestFn.generic(self, apiName, reqObj)
        if (rsp.status)
            throw new ZigbeeError('request unsuccess: ' + rsp.status, rsp.status)

        finalRsp.status = rsp.status;
        finalRsp.srcaddr = finalRsp.srcaddr || rsp.srcaddr;
        finalRsp[listKeys.entries] = finalRsp[listKeys.entries] || rsp[listKeys.entries];
        finalRsp[listKeys.listcount] = rsp[listKeys.listcount];
        finalRsp[listKeys.list] = finalRsp[listKeys.list].concat(rsp[listKeys.list]);

        totalToGet = totalToGet || (finalRsp[listKeys.entries] - finalRsp.startindex);
        accum = accum + rsp[listKeys.list].length;

        if (accum < totalToGet && rsp[listKeys.list].length) {
            nextIndex = nextIndex + rsp[listKeys.list].length;
            reqObj.startindex = nextIndex;
            return recursiveRequest();
        }
        return finalRsp
    };

    return recursiveRequest();
};

module.exports = Zdo;
<commit_msg>zdo fix<commit_after>/* jshint node: true */
'use strict';

const Q = require('q-lite'),
      Areq = require('areq'),
      CCZnp = require('cc-znp'),
      ZSC = CCZnp.constants,
      util = require('util'),
      EventEmitter = require('events').EventEmitter;

const zdoHelper = require('./zdo_helper'),
      ZigbeeError = require('../errors/zigbee_error');

const requestFn = {}

function Zdo(controller) {
    EventEmitter.call(this);
    this.setMaxListeners(40)
    this._controller = controller;
    this._areq = new Areq(controller, 48000);
}

util.inherits(Zdo, EventEmitter);

/*************************************************************************************************/
/*** Public APIs                                                                               ***/
/*************************************************************************************************/
Zdo.prototype.request = async function (apiName, valObj) {
    var requestType = zdoHelper.getRequestType(apiName);

    var handler = requestFn[requestType]
    if(!handler) {
        throw new Error('Unknown request type.')
    }
    return await handler(this, apiName, valObj)
};

/*************************************************************************************************/
/*** Protected Methods                                                                         ***/
/*************************************************************************************************/
Zdo.prototype._sendZdoRequestViaZnp = async function (apiName, valObj) {
    const rsp = await this._controller._znp.zdoRequest(apiName, valObj)
    if(!rsp){
        throw new Error(`Unable to execute ${apiName}`)
    }
    if (apiName !== 'startupFromApp' && rsp.status)
        throw new ZigbeeError('request unsuccess: ' + rsp.status, rsp.status)
    return rsp
};

requestFn.rspless = function (self, apiName, valObj) {
    return self._sendZdoRequestViaZnp(apiName, valObj);
};

requestFn.generic = async function (self, apiName, valObj) {
    var deferred = Q.defer(),
        areq = self._areq,
        areqEvtKey = zdoHelper.generateEventOfRequest(apiName, valObj),
        abortKey

    function handleAbort(){
        deferred.reject("__abort__")
    }

    if (areqEvtKey) {
        abortKey = zdoHelper.generateDstAddrOfRequest(valObj)
        if(abortKey) self.once(abortKey, handleAbort)
        areq.register(areqEvtKey, deferred, function (payload) {
            areq.resolve(areqEvtKey, payload);
        });
    }

    try {
        await self._sendZdoRequestViaZnp(apiName, valObj)
    } catch(err){
        areq.reject(areqEvtKey, err);
    }

    deferred.promise.catch(()=>{}).then(()=>{
        if(abortKey) self.removeListener(abortKey, handleAbort)
    })

    return await deferred.promise
};

requestFn.special = function (self, apiName, valObj) {
    if (apiName === 'serverDiscReq') {
        // broadcast, remote device may not response when no bits match in mask
        // listener at controller.on('ZDO:serverDiscRsp')
        return requestFn.rspless(self, 'serverDiscReq', valObj);
    } else if (apiName === 'bindReq') {
        if (valObj.dstaddrmode === ZSC.AF.addressMode.ADDR_16BIT)
            throw new Error('TI not support address 16bit mode.');
        else
            return requestFn.rspless(self, 'bindReq', valObj);
    } else if (apiName === 'mgmtPermitJoinReq') {
        if (valObj.dstaddr === 0xFFFC)  // broadcast to all routers (and coord), no waiting for AREQ rsp
            return requestFn.rspless(self, 'mgmtPermitJoinReq', valObj);
        else
            return requestFn.generic(self, 'mgmtPermitJoinReq', valObj);
    } else {
        throw new Error('No such request.')
    }
};

requestFn.concat = function (self, apiName, valObj) {
    if (apiName === 'nwkAddrReq' || apiName === 'ieeeAddrReq')
        return requestFn._concatAddr(self, apiName, valObj);
    else if (apiName === 'mgmtNwkDiscReq')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'networkcount',
            listcount: 'networklistcount',
            list: 'networklist'
        });
    else if (apiName === 'mgmtLqiReq')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'neighbortableentries',
            listcount: 'neighborlqilistcount',
            list: 'neighborlqilist'
        });
    else if (apiName === 'mgmtRtgReq')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'routingtableentries',
            listcount: 'routingtablelistcount',
            list: 'routingtablelist'
        });
    else if (apiName === 'mgmtBindRsp')
        return requestFn._concatList(self, apiName, valObj, {
            entries: 'bindingtableentries',
            listcount: 'bindingtablelistcount',
            list: 'bindingtablelist'
        });
    else
        throw new Error('No such request.');
};

requestFn._concatAddr = function (self, apiName, valObj) {
    var totalToGet = null,
        accum = 0,
        nextIndex = valObj.startindex,
        reqObj = {
            reqtype: valObj.reqtype,
            startindex: valObj.startindex    // start from 0
        },
        finalRsp = {
            status: null,
            ieeeaddr: null,
            nwkaddr: null,
            startindex: valObj.startindex,
            numassocdev: null,
            assocdevlist: []
        };

    if (apiName === 'nwkAddrReq')
        reqObj.ieeeaddr = valObj.ieeeaddr;
    else
        reqObj.shortaddr = valObj.shortaddr;

    async function recursiveRequest() {
        const rsp = await requestFn.generic(self, apiName, reqObj)
        if (rsp.status) {
            throw new ZigbeeError('request unsuccess: ' + rsp.status, rsp.status)
        }
        finalRsp.status = rsp.status;
        finalRsp.ieeeaddr = finalRsp.ieeeaddr || rsp.ieeeaddr;
        finalRsp.nwkaddr = finalRsp.nwkaddr || rsp.nwkaddr;
        finalRsp.numassocdev = finalRsp.numassocdev || rsp.numassocdev;
        finalRsp.assocdevlist = finalRsp.assocdevlist.concat(rsp.assocdevlist);

        totalToGet = totalToGet || (finalRsp.numassocdev - finalRsp.startindex);    // compute at 1st rsp back
        accum = accum + rsp.assocdevlist.length;

        if (valObj.reqtype === 1 && accum < totalToGet) {  // extended, include associated devices
            nextIndex = nextIndex + rsp.assocdevlist.length;
            reqObj.startindex = nextIndex;
            return await recursiveRequest();
        }
        return finalRsp
    };

    return recursiveRequest();
};

requestFn._concatList = function (self, apiName, valObj, listKeys) {
    // valObj = { dstaddr[, scanchannels, scanduration], startindex }
    // listKeys = { entries: 'networkcount', listcount: 'networklistcount', list: 'networklist' };
    var totalToGet = null,
        accum = 0,
        nextIndex = valObj.startindex,
        reqObj = {
            dstaddr: valObj.dstaddr,
            scanchannels: valObj.scanchannels,
            scanduration: valObj.scanduration,
            startindex: valObj.startindex    // starts from 0
        },
        finalRsp = {
            srcaddr: null,
            status: null,
            startindex: valObj.startindex
        };

    finalRsp[listKeys.entries] = null;       // finalRsp.networkcount = null
    finalRsp[listKeys.listcount] = null;     // finalRsp.networklistcount = null
    finalRsp[listKeys.list] = [];            // finalRsp.networklist = []

    if (apiName === 'mgmtNwkDiscReq') {
        reqObj.scanchannels = valObj.scanchannels;
        reqObj.scanduration = valObj.scanduration;
    }

    async function recursiveRequest () {
        const rsp = await requestFn.generic(self, apiName, reqObj)
        if (rsp.status)
            throw new ZigbeeError('request unsuccess: ' + rsp.status, rsp.status)

        finalRsp.status = rsp.status;
        finalRsp.srcaddr = finalRsp.srcaddr || rsp.srcaddr;
        finalRsp[listKeys.entries] = finalRsp[listKeys.entries] || rsp[listKeys.entries];
        finalRsp[listKeys.listcount] = rsp[listKeys.listcount];
        finalRsp[listKeys.list] = finalRsp[listKeys.list].concat(rsp[listKeys.list]);

        totalToGet = totalToGet || (finalRsp[listKeys.entries] - finalRsp.startindex);
        accum = accum + rsp[listKeys.list].length;

        if (accum < totalToGet && rsp[listKeys.list].length) {
            nextIndex = nextIndex + rsp[listKeys.list].length;
            reqObj.startindex = nextIndex;
            return recursiveRequest();
        }
        return finalRsp
    };

    return recursiveRequest();
};

module.exports = Zdo;
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs-extra');
const path = require('path');

const getAllFilesIn = function (dir, filelist) {
  filelist = filelist || [];
  const files = fs.readdirSync(dir);
  files.forEach(function (file) {
    if (fs.statSync(path.join(dir, file)).isDirectory()) {
      filelist = getAllFilesIn(path.join(dir, file), filelist);
    } else {
      filelist.push('./' + path.join(dir, file));
    }
  });

  return filelist;
};

const cucumberOptsRequire = getAllFilesIn('./src/step-definitions/').concat([
  './src/support/world.js',
  './src/support/hooks.js',
]);

exports.config = {
  // ==================
  // Specify Test Files
  // ==================
  // Define which test specs should run. The pattern is relative to the directory
  // from which `wdio` was called. Notice that, if you are calling `wdio` from an
  // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
  // directory is where your package.json resides, so `wdio` will be called from there.
  specs: [
    './src/features/**/*.feature',
  ],

  // Patterns to exclude.
  // exclude: [
  //   'path/to/excluded/files'
  // ],

  // ============
  // Capabilities
  // ============
  // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
  // time. Depending on the number of capabilities, WebdriverIO launches several test
  // sessions. Within your capabilities you can overwrite the spec and exclude options in
  // order to group specific specs to a specific capability.
  //
  // First, you can define how many instances should be started at the same time. Let's
  // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
  // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
  // files and you set maxInstances to 10, all spec files will get tested at the same time
  // and 30 processes will get spawned. The property handles how many capabilities
  // from the same test should run tests.
  //
  maxInstances: 1,

  // If you have trouble getting all important capabilities together, check out the
  // Sauce Labs platform configurator - a great tool to configure your capabilities:
  // https://docs.saucelabs.com/reference/platforms-configurator
  //
  capabilities: [
    {
      // maxInstances can get overwritten per capability. So if you have an in-house Selenium
      // grid with only 5 firefox instance available you can make sure that not more than
      // 5 instance gets started at a time.
      maxInstances: 1,

      browserName: 'chrome',
      chromeOptions: {
        // args: ['--headless'], // To run in headless mode
        prefs: {
          profile: {
            default_content_setting_values: { images: 2 }, // Do not load images for tests to run faster
            password_manager_enabled: false, // Deactivate password manager
          },
          credentials_enable_service: false, // Deactivate password manager
        },
      },

      proxy: {
        proxyType: 'system',
      },
    },

    // {
    //   maxInstances: 1,
    //
    //   browserName: 'firefox',
    //   acceptInsecureCerts: true,
    //
    //   proxy: {
    //     proxyType: 'system',
    //   },
    //
    // },
  ],

  // ===================
  // Test Configurations
  // ===================
  // Define all options that are relevant for the WebdriverIO instance here
  //
  // By default WebdriverIO commands are executed in a synchronous way using
  // the wdio-sync package. If you still want to run your tests in an async way
  // e.g. using promises you can set the sync option to false.
  sync: false,

  // Level of logging verbosity: silent | verbose | command | data | result | error
  logLevel: 'error',
  logOutput: './log',

  // Enables colors for log output.
  coloredLogs: true,

  // Saves a screenshot to a given path if a command fails.
  //screenshotPath: './errorShots/',
  //
  // Set a base URL in order to shorten url command calls. If your url parameter starts
  // with "/", then the base url gets prepended.
  baseUrl: 'http://localhost:8080',

  // Default timeout for all waitFor* commands.
  waitforTimeout: 10000,

  // Default timeout in milliseconds for request
  // if Selenium Grid doesn't send response
  connectionRetryTimeout: 90000,

  // Default request retries count
  connectionRetryCount: 0,

  // Initialize the browser instance with a WebdriverIO plugin. The object should have the
  // plugin name as key and the desired plugin options as properties. Make sure you have
  // the plugin installed before running any tests. The following plugins are currently
  // available:
  // WebdriverCSS: https://github.com/webdriverio/webdrivercss
  // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
  // Browserevent: https://github.com/webdriverio/browserevent
  // plugins: {
  //     webdrivercss: {
  //         screenshotRoot: 'my-shots',
  //         failedComparisonsRoot: 'diffs',
  //         misMatchTolerance: 0.05,
  //         screenWidth: [320,480,640,1024]
  //     },
  //     webdriverrtc: {},
  //     browserevent: {}
  // },
  //
  // Test runner services
  // Services take over a specific job you don't want to take care of. They enhance
  // your test setup with almost no effort. Unlike plugins, they don't add new
  // commands. Instead, they hook themselves up into the test process.
  services: ['selenium-standalone'],
  seleniumLogs: './log/selenium.log',
  port: '4444',

  seleniumInstallArgs: {
    // proxy: 'http://localhost:3128',
    drivers: {
      chrome: {
        version: '2.31',
        arch: process.arch,
        baseURL: 'https://chromedriver.storage.googleapis.com',
      },
      firefox: {
        version: '0.18.0',
        arch: process.arch,
        baseURL: 'https://github.com/mozilla/geckodriver/releases/download',
      },
    },
  },

  seleniumArgs: {
    drivers: {
      chrome: {
        version: '2.31',
        arch: process.arch,
      },
      firefox: {
        version: '0.18.0',
        arch: process.arch,
      },
    },
  },

  // Framework you want to run your specs with.
  // The following are supported: Mocha, Jasmine, and Cucumber
  // see also: http://webdriver.io/guide/testrunner/frameworks.html
  //
  // Make sure you have the wdio adapter package for the specific framework installed
  // before running any tests.
  framework: 'cucumber',

  // Test reporter for stdout.
  // The only one supported by default is 'dot'
  // see also: http://webdriver.io/guide/testrunner/reporters.html
  reporters: ['cucumber', 'junit', 'json', 'allure'],
  reporterOptions: {
    json: {
      outputDir: 'output/json',
    },
    junit: {
      outputDir: 'output/junit-results',
    },
    allure: {
      outputDir: 'output/allure',
    },
  },

  // If you are using Cucumber you need to specify the location of your step definitions.
  cucumberOpts: {

    // DO NOT REQUIRE scripts that should be included and executed at runtime,
    // otherwise there will be loaded and executed first and will raise weird issues.
    require: cucumberOptsRequire,

    // <boolean> show full backtrace for errors
    backtrace: true,

    // <string[]> ("extension:module") require files with the given EXTENSION after requiring MODULE (repeatable)
    compiler: [],

    // <boolean> invoke formatters without executing steps
    dryRun: false,

    // <boolean> abort the run on first failure
    failFast: false,

    // <string[]> (type[:path]) specify the output format, optionally supply PATH to redirect formatter output (repeatable)
    format: 'pretty',

    // <boolean> disable colors in formatter output
    colors: true,

    // <boolean> hide step definition snippets for pending steps
    snippets: true,

    // <boolean> hide source uris
    source: true,

    // <string[]> (name) specify the profile to use
    profile: [],

    // <boolean> fail if there are any undefined or pending steps
    strict: false,

    // <tag-expression> only execute the features or scenarios with tags matching the expression
    // tagExpression: '@smoke',

    // <number> timeout for step definitions
    timeout: 30000,

    // <boolean> Enable this config to treat undefined definitions as warnings.
    ignoreUndefinedDefinitions: false,
  },

  // Size of the browser window
  windowHandleSize: {
    height: 800,
    width: 1280,
  },

  // Additional timeouts. See http://webdriver.io/guide/testrunner/timeouts.html for more details
  timeouts: {
    script: 3000,
    implicit: 3000,
  },

  // =====
  // Hooks
  // =====
  // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
  // it and to build services around it. You can either apply a single function or an array of
  // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
  // resolved to continue.
  //
  // Gets executed once before all workers get launched.
  // onPrepare: function (config, capabilities) {
  // },
  //
  // Gets executed before test execution begins. At this point you can access all global
  // variables, such as `browser`. It is the perfect place to define custom commands.
  before() {
    browser.windowHandleSize({
      width: browser.options.windowHandleSize.width,
      height: browser.options.windowHandleSize.height,
    });
  },

  // Hook that gets executed before the suite starts
  // beforeSuite: function (suite) {
  // },
  //
  // Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
  // beforeEach in Mocha)
  // beforeHook: function () {
  // },
  //
  // Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
  // afterEach in Mocha)
  // afterHook: function () {
  // },
  //
  // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
  // beforeTest: function (test) {
  // },
  //
  // Runs before a WebdriverIO command gets executed.
  // beforeCommand: function (commandName, args) {
  // },
  //
  // Runs after a WebdriverIO command gets executed
  // afterCommand: function (commandName, args, result, error) {
  // },
  //
  // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
  // afterTest: function (test) {
  // },
  //
  // Hook that gets executed after the suite has ended
  // afterSuite: function (suite) {
  // },
  //
  // Gets executed after all tests are done. You still have access to all global variables from
  // the test.
  // after: function (result, capabilities, specs) {
  // },
  //
  // Gets executed after all workers got shut down and the process is about to exit. It is not
  // possible to defer the end of the process using a promise.
  // onComplete: function(exitCode) {
  // }

};
<commit_msg>Fix SonarQube issues<commit_after>'use strict';

const fs = require('fs-extra');
const path = require('path');

const getAllFilesIn = function (dir, filelist) {
  let files = filelist || [];
  const dirFiles = fs.readdirSync(dir);
  dirFiles.forEach(function (file) {
    if (fs.statSync(path.join(dir, file)).isDirectory()) {
      files = getAllFilesIn(path.join(dir, file), files);
    } else {
      files.push('./' + path.join(dir, file));
    }
  });

  return files;
};

const cucumberOptsRequire = getAllFilesIn('./src/step-definitions/').concat([
  './src/support/world.js',
  './src/support/hooks.js',
]);

exports.config = {
  // ==================
  // Specify Test Files
  // ==================
  // Define which test specs should run. The pattern is relative to the directory
  // from which `wdio` was called. Notice that, if you are calling `wdio` from an
  // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
  // directory is where your package.json resides, so `wdio` will be called from there.
  specs: [
    './src/features/**/*.feature',
  ],

  // Patterns to exclude.
  // exclude: [
  //   'path/to/excluded/files'
  // ],

  // ============
  // Capabilities
  // ============
  // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
  // time. Depending on the number of capabilities, WebdriverIO launches several test
  // sessions. Within your capabilities you can overwrite the spec and exclude options in
  // order to group specific specs to a specific capability.
  //
  // First, you can define how many instances should be started at the same time. Let's
  // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
  // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
  // files and you set maxInstances to 10, all spec files will get tested at the same time
  // and 30 processes will get spawned. The property handles how many capabilities
  // from the same test should run tests.
  //
  maxInstances: 1,

  // If you have trouble getting all important capabilities together, check out the
  // Sauce Labs platform configurator - a great tool to configure your capabilities:
  // https://docs.saucelabs.com/reference/platforms-configurator
  //
  capabilities: [
    {
      // maxInstances can get overwritten per capability. So if you have an in-house Selenium
      // grid with only 5 firefox instance available you can make sure that not more than
      // 5 instance gets started at a time.
      maxInstances: 1,

      browserName: 'chrome',
      chromeOptions: {
        // args: ['--headless'], // To run in headless mode
        prefs: {
          profile: {
            default_content_setting_values: { images: 2 }, // Do not load images for tests to run faster
            password_manager_enabled: false, // Deactivate password manager
          },
          credentials_enable_service: false, // Deactivate password manager
        },
      },

      proxy: {
        proxyType: 'system',
      },
    },

    // {
    //   maxInstances: 1,
    //
    //   browserName: 'firefox',
    //   acceptInsecureCerts: true,
    //
    //   proxy: {
    //     proxyType: 'system',
    //   },
    //
    // },
  ],

  // ===================
  // Test Configurations
  // ===================
  // Define all options that are relevant for the WebdriverIO instance here
  //
  // By default WebdriverIO commands are executed in a synchronous way using
  // the wdio-sync package. If you still want to run your tests in an async way
  // e.g. using promises you can set the sync option to false.
  sync: false,

  // Level of logging verbosity: silent | verbose | command | data | result | error
  logLevel: 'error',
  logOutput: './log',

  // Enables colors for log output.
  coloredLogs: true,

  // Saves a screenshot to a given path if a command fails.
  //screenshotPath: './errorShots/',
  //
  // Set a base URL in order to shorten url command calls. If your url parameter starts
  // with "/", then the base url gets prepended.
  baseUrl: 'http://localhost:8080',

  // Default timeout for all waitFor* commands.
  waitforTimeout: 10000,

  // Default timeout in milliseconds for request
  // if Selenium Grid doesn't send response
  connectionRetryTimeout: 90000,

  // Default request retries count
  connectionRetryCount: 0,

  // Initialize the browser instance with a WebdriverIO plugin. The object should have the
  // plugin name as key and the desired plugin options as properties. Make sure you have
  // the plugin installed before running any tests. The following plugins are currently
  // available:
  // WebdriverCSS: https://github.com/webdriverio/webdrivercss
  // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
  // Browserevent: https://github.com/webdriverio/browserevent
  // plugins: {
  //     webdrivercss: {
  //         screenshotRoot: 'my-shots',
  //         failedComparisonsRoot: 'diffs',
  //         misMatchTolerance: 0.05,
  //         screenWidth: [320,480,640,1024]
  //     },
  //     webdriverrtc: {},
  //     browserevent: {}
  // },
  //
  // Test runner services
  // Services take over a specific job you don't want to take care of. They enhance
  // your test setup with almost no effort. Unlike plugins, they don't add new
  // commands. Instead, they hook themselves up into the test process.
  services: ['selenium-standalone'],
  seleniumLogs: './log/selenium.log',
  port: '4444',

  seleniumInstallArgs: {
    // proxy: 'http://localhost:3128',
    drivers: {
      chrome: {
        version: '2.31',
        arch: process.arch,
        baseURL: 'https://chromedriver.storage.googleapis.com',
      },
      firefox: {
        version: '0.18.0',
        arch: process.arch,
        baseURL: 'https://github.com/mozilla/geckodriver/releases/download',
      },
    },
  },

  seleniumArgs: {
    drivers: {
      chrome: {
        version: '2.31',
        arch: process.arch,
      },
      firefox: {
        version: '0.18.0',
        arch: process.arch,
      },
    },
  },

  // Framework you want to run your specs with.
  // The following are supported: Mocha, Jasmine, and Cucumber
  // see also: http://webdriver.io/guide/testrunner/frameworks.html
  //
  // Make sure you have the wdio adapter package for the specific framework installed
  // before running any tests.
  framework: 'cucumber',

  // Test reporter for stdout.
  // The only one supported by default is 'dot'
  // see also: http://webdriver.io/guide/testrunner/reporters.html
  reporters: ['cucumber', 'junit', 'json', 'allure'],
  reporterOptions: {
    json: {
      outputDir: 'output/json',
    },
    junit: {
      outputDir: 'output/junit-results',
    },
    allure: {
      outputDir: 'output/allure',
    },
  },

  // If you are using Cucumber you need to specify the location of your step definitions.
  cucumberOpts: {

    // DO NOT REQUIRE scripts that should be included and executed at runtime,
    // otherwise there will be loaded and executed first and will raise weird issues.
    require: cucumberOptsRequire,

    // <boolean> show full backtrace for errors
    backtrace: true,

    // <string[]> ("extension:module") require files with the given EXTENSION after requiring MODULE (repeatable)
    compiler: [],

    // <boolean> invoke formatters without executing steps
    dryRun: false,

    // <boolean> abort the run on first failure
    failFast: false,

    // <string[]> (type[:path]) specify the output format, optionally supply PATH to redirect formatter output (repeatable)
    format: 'pretty',

    // <boolean> disable colors in formatter output
    colors: true,

    // <boolean> hide step definition snippets for pending steps
    snippets: true,

    // <boolean> hide source uris
    source: true,

    // <string[]> (name) specify the profile to use
    profile: [],

    // <boolean> fail if there are any undefined or pending steps
    strict: false,

    // <tag-expression> only execute the features or scenarios with tags matching the expression
    // tagExpression: '@smoke',

    // <number> timeout for step definitions
    timeout: 30000,

    // <boolean> Enable this config to treat undefined definitions as warnings.
    ignoreUndefinedDefinitions: false,
  },

  // Size of the browser window
  windowHandleSize: {
    height: 800,
    width: 1280,
  },

  // Additional timeouts. See http://webdriver.io/guide/testrunner/timeouts.html for more details
  timeouts: {
    script: 3000,
    implicit: 3000,
  },

  // =====
  // Hooks
  // =====
  // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
  // it and to build services around it. You can either apply a single function or an array of
  // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
  // resolved to continue.
  //
  // Gets executed once before all workers get launched.
  // onPrepare: function (config, capabilities) {
  // },
  //
  // Gets executed before test execution begins. At this point you can access all global
  // variables, such as `browser`. It is the perfect place to define custom commands.
  before() {
    browser.windowHandleSize({
      width: browser.options.windowHandleSize.width,
      height: browser.options.windowHandleSize.height,
    });
  },

  // Hook that gets executed before the suite starts
  // beforeSuite: function (suite) {
  // },
  //
  // Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
  // beforeEach in Mocha)
  // beforeHook: function () {
  // },
  //
  // Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
  // afterEach in Mocha)
  // afterHook: function () {
  // },
  //
  // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
  // beforeTest: function (test) {
  // },
  //
  // Runs before a WebdriverIO command gets executed.
  // beforeCommand: function (commandName, args) {
  // },
  //
  // Runs after a WebdriverIO command gets executed
  // afterCommand: function (commandName, args, result, error) {
  // },
  //
  // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
  // afterTest: function (test) {
  // },
  //
  // Hook that gets executed after the suite has ended
  // afterSuite: function (suite) {
  // },
  //
  // Gets executed after all tests are done. You still have access to all global variables from
  // the test.
  // after: function (result, capabilities, specs) {
  // },
  //
  // Gets executed after all workers got shut down and the process is about to exit. It is not
  // possible to defer the end of the process using a promise.
  // onComplete: function(exitCode) {
  // }

};
<|endoftext|>


<commit_before>const Dict = require('./dict').Dict;

// These colors are used now for streams.
const stream_colors = [
    "#76ce90", "#fae589", "#a6c7e5", "#e79ab5",
    "#bfd56f", "#f4ae55", "#b0a5fd", "#addfe5",
    "#f5ce6e", "#c2726a", "#94c849", "#bd86e5",
    "#ee7e4a", "#a6dcbf", "#95a5fd", "#53a063",
    "#9987e1", "#e4523d", "#c2c2c2", "#4f8de4",
    "#c6a8ad", "#e7cc4d", "#c8bebf", "#a47462",
];

// Shuffle our colors on page load to prevent
// bias toward "early" colors.
exports.colors = _.shuffle(stream_colors);

exports.reset = function () {
    exports.unused_colors = exports.colors.slice();
};

exports.reset();

exports.claim_color = function (color) {
    const i = exports.unused_colors.indexOf(color);

    if (i < 0) {
        return;
    }

    exports.unused_colors.splice(i, 1);

    if (exports.unused_colors.length === 0) {
        exports.reset();
    }
};

exports.claim_colors = function (subs) {
    const used_colors = new Dict();

    _.each(subs, function (sub) {
        if (sub.color) {
            used_colors.set(sub.color, true);
        }
    });

    _.each(used_colors.keys(), function (color) {
        exports.claim_color(color);
    });
};

exports.pick_color = function () {
    const color = exports.unused_colors[0];

    exports.claim_color(color);

    return color;
};

window.color_data = exports;
<commit_msg>streams: Simplify claim_colors.<commit_after>// These colors are used now for streams.
const stream_colors = [
    "#76ce90", "#fae589", "#a6c7e5", "#e79ab5",
    "#bfd56f", "#f4ae55", "#b0a5fd", "#addfe5",
    "#f5ce6e", "#c2726a", "#94c849", "#bd86e5",
    "#ee7e4a", "#a6dcbf", "#95a5fd", "#53a063",
    "#9987e1", "#e4523d", "#c2c2c2", "#4f8de4",
    "#c6a8ad", "#e7cc4d", "#c8bebf", "#a47462",
];

// Shuffle our colors on page load to prevent
// bias toward "early" colors.
exports.colors = _.shuffle(stream_colors);

exports.reset = function () {
    exports.unused_colors = exports.colors.slice();
};

exports.reset();

exports.claim_color = function (color) {
    const i = exports.unused_colors.indexOf(color);

    if (i < 0) {
        return;
    }

    exports.unused_colors.splice(i, 1);

    if (exports.unused_colors.length === 0) {
        exports.reset();
    }
};

exports.claim_colors = function (subs) {
    const colors = new Set(_.pluck(subs, 'color'));
    colors.forEach(exports.claim_color);
};

exports.pick_color = function () {
    const color = exports.unused_colors[0];

    exports.claim_color(color);

    return color;
};

window.color_data = exports;
<|endoftext|>


<commit_before>"use strict";

module.exports = {
  env: { browser: true },
  plugins: ["import", "unicorn"],
  rules: {
    "import/no-nodejs-modules": "error",

    "unicorn/no-document-cookie": "error",
    "unicorn/prefer-add-event-listener": "error",
    "unicorn/prefer-dom-node-append": "error",
    "unicorn/prefer-dom-node-dataset": "error",
    "unicorn/prefer-dom-node-remove": "error",
    "unicorn/prefer-dom-node-text-content": "error",
    "unicorn/prefer-keyboard-event-key": "error",
    "unicorn/prefer-modern-dom-apis": "error",
    "unicorn/prefer-query-selector": "error",
    "unicorn/require-post-message-target-origin": "error",
    "unicorn/no-invalid-remove-event-listener": "error",
  },
};
<commit_msg>fix: browser should not report about node modules<commit_after>"use strict";

module.exports = {
  env: { browser: true },
  plugins: ["import", "unicorn"],
  rules: {
    "unicorn/no-document-cookie": "error",
    "unicorn/prefer-add-event-listener": "error",
    "unicorn/prefer-dom-node-append": "error",
    "unicorn/prefer-dom-node-dataset": "error",
    "unicorn/prefer-dom-node-remove": "error",
    "unicorn/prefer-dom-node-text-content": "error",
    "unicorn/prefer-keyboard-event-key": "error",
    "unicorn/prefer-modern-dom-apis": "error",
    "unicorn/prefer-query-selector": "error",
    "unicorn/require-post-message-target-origin": "error",
    "unicorn/no-invalid-remove-event-listener": "error",
  },
};
<|endoftext|>


<commit_before>'use strict';

var module = angular.module('app.directives', []);

module.directive('aceEditor',
    function (editor) {
        return {
            restrict:'A',
            link:function (scope, element) {
                editor.rebind(element[0]);
            }
        };
    });

module.directive('html5VideoPlayer',
    function (video) {
        return {
            restrict:'A',
            link:function (scope, element) {
                video.bindVideoPlayer(element[0]);
            }
        };
    });

module.directive('star',
    function () {
        return {
            restrict:'E',
            replace:true,
            scope:{
                val:'=value',
                // Value bound to
                eventFn:'&click'
                // Optional expression evaluated on click
            },
            link:function (scope, element) {
                element.bind('click',
                    function () {
                        scope.$apply(function () {
                            scope.val = !scope.val;
                        });
                        scope.$eval(scope.eventFn, scope.val);
                    });
            },
            template:''
        }
    });

module.directive('alert',
    function ($rootScope, analytics) {
        return {
            restrict:'E',
            replace:true,
            link:function (scope, element) {
                $rootScope.$on('error',
                    function (event, data) {
                        scope.$apply(function () {
                            analytics.pushAnalytics('Error', data.message);
                            scope.message = data.message;
                            $(element).show();
                        });
                    });
                scope.close = function () {
                    $(element).hide();
                };
            },
            template:'
' +
                '  ×' +
                '  {{message}}' +
                '
'
        }
    });

module.directive('playPauseVideoHtml5', function(video) {
    return {
        link: function(scope, element, attrs) {
            scope.$watch(attrs.playPauseVideoHtml5, function(newValue, oldValue) {
                if(newValue != oldValue && video.player) {
                    newValue ? video.player.play() : video.player.pause();
                }
            });
        }
    }
});

module.directive('playPauseVideoYoutube', function(youtubePlayerApi) {
    return {
        link: function(scope, element, attrs) {
            scope.$watch(attrs.playPauseVideoYoutube, function(newValue, oldValue) {
                if(newValue != oldValue && youtubePlayerApi.player) {
                    newValue ? youtubePlayerApi.player.playVideo() : youtubePlayerApi.player.pauseVideo();
                }
            });
        }
    }
});

module.directive('bootstrapTooltip', function(youtubePlayerApi) {
    return {
        restrict:'A',
        link: function(scope, element, attrs) {
            $(element).tooltip();
        }
    }
});<commit_msg>Bootstrap switch directive<commit_after>'use strict';

var module = angular.module('app.directives', []);

module.directive('aceEditor',
    function (editor) {
        return {
            restrict:'A',
            link:function (scope, element) {
                editor.rebind(element[0]);
            }
        };
    });

module.directive('html5VideoPlayer',
    function (video) {
        return {
            restrict:'A',
            link:function (scope, element) {
                video.bindVideoPlayer(element[0]);
            }
        };
    });

module.directive('star',
    function () {
        return {
            restrict:'E',
            replace:true,
            scope:{
                val:'=value',
                // Value bound to
                eventFn:'&click'
                // Optional expression evaluated on click
            },
            link:function (scope, element) {
                element.bind('click',
                    function () {
                        scope.$apply(function () {
                            scope.val = !scope.val;
                        });
                        scope.$eval(scope.eventFn, scope.val);
                    });
            },
            template:''
        }
    });

module.directive('alert',
    function ($rootScope, analytics) {
        return {
            restrict:'E',
            replace:true,
            link:function (scope, element) {
                $rootScope.$on('error',
                    function (event, data) {
                        scope.$apply(function () {
                            analytics.pushAnalytics('Error', data.message);
                            scope.message = data.message;
                            $(element).show();
                        });
                    });
                scope.close = function () {
                    $(element).hide();
                };
            },
            template:'
' +
                '  ×' +
                '  {{message}}' +
                '
'
        }
    });

module.directive('playPauseVideoHtml5', function(video) {
    return {
        link: function(scope, element, attrs) {
            scope.$watch(attrs.playPauseVideoHtml5, function(newValue, oldValue) {
                if(newValue != oldValue && video.player) {
                    newValue ? video.player.play() : video.player.pause();
                }
            });
        }
    }
});

module.directive('playPauseVideoYoutube', function(youtubePlayerApi) {
    return {
        link: function(scope, element, attrs) {
            scope.$watch(attrs.playPauseVideoYoutube, function(newValue, oldValue) {
                if(newValue != oldValue && youtubePlayerApi.player) {
                    newValue ? youtubePlayerApi.player.playVideo() : youtubePlayerApi.player.pauseVideo();
                }
            });
        }
    }
});

module.directive('bootstrapTooltip', function(youtubePlayerApi) {
    return {
        restrict:'A',
        link: function(scope, element) {
            $(element).tooltip();
        }
    }
});

module.directive('bootstrapSwitch', function() {
    return {
        restrict:'E',
        scope: {
            property: "="
        },
        link: function(scope, element) {
            $(element).bootstrapSwitch();

            scope.$watch('property', function () {
                if(scope.property)
                    $(element).bootstrapSwitch('setState', scope.property);
            });

            $(element).on('switch-change', function (e, data) {
                scope.property = data.value;
                scope.$apply();
            });
        },
        replace: true,
        template:
            '
'
        +     '<input type="checkbox" />'
        +    '
'
    }
});<|endoftext|>