<commit_before>/*
  Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
  Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*global esprima:true, estraverse:true, escope: true,
 define:true, require:true, exports:true */

(function (root, factory) {
    'use strict';

    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
    // Rhino, and plain browser loading.
    if (typeof define === 'function' && define.amd) {
        define('esrefactor', ['exports', 'esprima', 'estraverse', 'escope'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('esprima'), require('estraverse'), require('escope'));
    } else {
        factory((root.esrefactor = {}), esprima, estraverse, escope);
    }
}(this, function (exports, esprima, estraverse, escope) {
    'use strict';

    function Context(code) {
        this._code = null;
        this._syntax = null;
        this._scopeManager = null;
        if (code) {
            this.setCode(code);
        }
    }

    Context.prototype.setCode = function (code) {
        this._code = null;
        this._syntax = null;
        this._scopeManager = null;
        if (typeof code === 'string') {
            this._code = code;
            this._syntax = esprima.parse(code, { range: true });
        } else if (typeof code === 'object' && code.type === 'Program') {
            if (typeof code.range !== 'object' || code.range.length !== 2) {
                throw new Error('esrefactor.Context only accepts a syntax tree with range information');
            }
            this._syntax = code;
        }
        this._scopeManager = escope.analyze(this._syntax);
    };

    function locateDeclaration(ref) {
        var scope, i, v;

        if (ref.resolved) {
            return ref.resolved.defs[ref.resolved.defs.length - 1].name;
        }

        scope = ref.from;
        do {
            for (i = 0; i < scope.variables.length; ++i) {
                v = scope.variables[i];
                if (v.name === ref.identifier.name && v.defs.length) {
                    return v.defs[v.defs.length - 1].name;
                }
            }
            scope = scope.upper;
        } while (scope);

        return null;
    }

    // Given the scope, look up the reference which corresponds to the identifier
    // in the specified cursor position. If possible, find also the corresponding
    // declaration for that reference.

    Context.prototype._lookup = function (scope, identifier) {
        var i, j, ref;

        for (i = 0; i < scope.references.length; ++i) {
            ref = scope.references[i];
            if (ref.identifier === identifier) {
                return {
                    identifier: identifier,
                    declaration: locateDeclaration(ref)
                };
            }
        }

        for (i = 0; i < scope.variableScope.variables.length; ++i) {
            ref = scope.variableScope.variables[i];
            for (j = 0; j < ref.identifiers.length; ++j) {
                if (ref.identifiers[j] === identifier) {
                    return {
                        identifier: identifier,
                        declaration: identifier
                    };
                }
            }
        }

    };

    Context.prototype._getScopeByPosition = function(pos) {
        var scope;
        var scopeManager = this._scopeManager;
        scopeManager.attach();
        estraverse.traverse(this._syntax, {
            enter: function (node) {
              scope = scopeManager.acquire(node) || scope;
              if (node.type === esprima.Syntax.Identifier) {
                  if (node.range[0] <= pos && node.range[1] >= pos) {
                      return estraverse.VisitorOption.Break;
                  }
              }
            },
            leave: function (node) {
                scope = scopeManager.release(node) || scope;
            }
        });
        scopeManager.detach();
        return scope;
    };

    Context.prototype._getIdentifierNodeByPosition = function(pos) {
        var identifierNode;
        this._scopeManager.attach();
        estraverse.traverse(this._syntax, {
            enter: function (node) {
                if (node.type === esprima.Syntax.Identifier) {
                    if (node.range[0] <= pos && node.range[1] >= pos) {
                        identifierNode = node;
                        return estraverse.VisitorOption.Break;
                    }
                }
            }
        });
        this._scopeManager.detach();
        return identifierNode;
    };

    // Given the cursor position, locate the identifier in that position.
    // If there is no identifier in that position, undefined will be returned.
    //
    // The returned object will have the following property:
    //    identifier: the syntax node associated with the identifier
    //   declaration: the syntax node where the identifier is declared
    //    references: an array of the references of the identifier
    //
    // Note that the references array also includes the identifier but it
    // does exclude the declaration.
    //
    // Example:
    //     var ctx = new esrefactor.Context('var x; x; x = 42');
    //     var id = ctx.identify(10);
    //
    //  id will have the value of:
    //      {
    //        identifier: {
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [10, 11]
    //        },
    //        declaration: {
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [4, 5]
    //        },
    //        references: [{
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [7, 8]
    //        }, {
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [10, 11]
    //        }]
    //      }

    Context.prototype.identify = function (pos) {
        if (!this._syntax) {
            throw new Error('Unable to identify anything without a syntax tree');
        }
        if (!this._scopeManager) {
            throw new Error('Unable to identify anything without a valid scope manager');
        }
        var identifier = this._getIdentifierNodeByPosition(pos);
        if (!identifier) {
            return;
        }
        var scope = this._getScopeByPosition(pos);
        var result = this._lookup(scope, identifier);
        if (result) {
            result.references = this._findAllReferencesInScope(result.declaration, identifier);
        }
        return result;
    };

    // Search for all other identical references (same scope).
    Context.prototype._findAllReferencesInScope = function(declaration, identifier) {
        var scopeManager = this._scopeManager;
        var lookup = this._lookup;
        var references = [];
        scopeManager.attach();
        estraverse.traverse(this._syntax, {
            enter: function (node) {
                var scope, i, ref, d;
                scope = scopeManager.acquire(node);
                for (i = 0; i < (scope ? scope.references.length : 0); ++i) {
                    ref = scope.references[i];
                    if (ref.identifier.name === identifier.name) {
                        d = lookup(scope, ref.identifier);
                        if (d && d.declaration === declaration) {
                            references.push(ref.identifier);
                        }
                    }
                }
            }
        });
        scopeManager.detach();
        return references;
    };

    // Rename the identifier and its reference to a new specific name.
    // The return value is the new code after the identifier is renamed.
    //
    // This functions needs identification, which is obtain using identify() function.
    //
    // Example:
    //   var ctx = new esrefactor.Context('var x; x = 42');
    //   var id = ctx.identify(4);
    //   var code = ctx.rename(id, 'y');
    //
    // code will be `var y; y = 42'.

    Context.prototype.rename = function (identification, name) {
        var result, list, set, i, id, entry;

        if (!this._code) {
            throw new Error('Unable to rename without the original source');
        }

        result = this._code;
        if (typeof identification === 'undefined') {
            return result;
        }

        list = [identification.identifier.range];
        if (identification.declaration) {
            list.push(identification.declaration.range);
        }
        for (i = 0; i < identification.references.length; ++i) {
            list.push(identification.references[i].range);
        }

        // Sort the references based on the position to prevent
        // shifting all the ranges.
        list.sort(function (a, b) { return b[0] - a[0]; });

        // Prevent double renaming, get the unique set.
        set = [];
        set.push(list[0]);
        for (i = 1; i < list.length; ++i) {
            if (list[i][0] !== list[i - 1][0]) {
                set.push(list[i]);
            }
        }

        id = identification.identifier.name;
        for (i = 0; i < set.length; ++i) {
            result = result.slice(0, set[i][0]) + name + result.slice(set[i][1]);
        }

        return result;
    };

    exports.Context = Context;
}));
<commit_msg>Separate declaration lookup.<commit_after>/*
  Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
  Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*global esprima:true, estraverse:true, escope: true,
 define:true, require:true, exports:true */

(function (root, factory) {
    'use strict';

    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
    // Rhino, and plain browser loading.
    if (typeof define === 'function' && define.amd) {
        define('esrefactor', ['exports', 'esprima', 'estraverse', 'escope'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('esprima'), require('estraverse'), require('escope'));
    } else {
        factory((root.esrefactor = {}), esprima, estraverse, escope);
    }
}(this, function (exports, esprima, estraverse, escope) {
    'use strict';

    function Context(code) {
        this._code = null;
        this._syntax = null;
        this._scopeManager = null;
        if (code) {
            this.setCode(code);
        }
    }

    Context.prototype.setCode = function (code) {
        this._code = null;
        this._syntax = null;
        this._scopeManager = null;
        if (typeof code === 'string') {
            this._code = code;
            this._syntax = esprima.parse(code, { range: true });
        } else if (typeof code === 'object' && code.type === 'Program') {
            if (typeof code.range !== 'object' || code.range.length !== 2) {
                throw new Error('esrefactor.Context only accepts a syntax tree with range information');
            }
            this._syntax = code;
        }
        this._scopeManager = escope.analyze(this._syntax);
    };

    function locateDeclaration(ref) {
        var scope, i, v;

        if (ref.resolved) {
            return ref.resolved.defs[ref.resolved.defs.length - 1].name;
        }

        scope = ref.from;
        do {
            for (i = 0; i < scope.variables.length; ++i) {
                v = scope.variables[i];
                if (v.name === ref.identifier.name && v.defs.length) {
                    return v.defs[v.defs.length - 1].name;
                }
            }
            scope = scope.upper;
        } while (scope);

        return null;
    }

    // Given the scope, look up the reference which corresponds to the identifier
    // in the specified cursor position. If possible, find also the corresponding
    // declaration for that reference.

    Context.prototype._lookup = function (scope, identifier) {
        var i, j, ref;

        for (i = 0; i < scope.references.length; ++i) {
            ref = scope.references[i];
            if (ref.identifier === identifier) {
                return {
                    identifier: identifier,
                    declaration: locateDeclaration(ref)
                };
            }
        }

        for (i = 0; i < scope.variableScope.variables.length; ++i) {
            ref = scope.variableScope.variables[i];
            for (j = 0; j < ref.identifiers.length; ++j) {
                if (ref.identifiers[j] === identifier) {
                    return {
                        identifier: identifier,
                        declaration: identifier
                    };
                }
            }
        }
    };

    Context.prototype._lookupDeclaration = function(scope, identifier) {
      var i, j, ref;
      for (i = 0; i < scope.references.length; ++i) {
          ref = scope.references[i];
          if (ref.identifier === identifier) {
              return locateDeclaration(ref);
          }
      }

      for (i = 0; i < scope.variableScope.variables.length; ++i) {
          ref = scope.variableScope.variables[i];
          for (j = 0; j < ref.identifiers.length; ++j) {
              if (ref.identifiers[j] === identifier) {
                  return identifier;
              }
          }
      }
    };

    Context.prototype._getScopeByPosition = function(pos) {
        var scope;
        var scopeManager = this._scopeManager;
        scopeManager.attach();
        estraverse.traverse(this._syntax, {
            enter: function (node) {
              scope = scopeManager.acquire(node) || scope;
              if (node.type === esprima.Syntax.Identifier) {
                  if (node.range[0] <= pos && node.range[1] >= pos) {
                      return estraverse.VisitorOption.Break;
                  }
              }
            },
            leave: function (node) {
                scope = scopeManager.release(node) || scope;
            }
        });
        scopeManager.detach();
        return scope;
    };

    Context.prototype._getIdentifierNodeByPosition = function(pos) {
        var identifierNode;
        this._scopeManager.attach();
        estraverse.traverse(this._syntax, {
            enter: function (node) {
                if (node.type === esprima.Syntax.Identifier) {
                    if (node.range[0] <= pos && node.range[1] >= pos) {
                        identifierNode = node;
                        return estraverse.VisitorOption.Break;
                    }
                }
            }
        });
        this._scopeManager.detach();
        return identifierNode;
    };

    // Given the cursor position, locate the identifier in that position.
    // If there is no identifier in that position, undefined will be returned.
    //
    // The returned object will have the following property:
    //    identifier: the syntax node associated with the identifier
    //   declaration: the syntax node where the identifier is declared
    //    references: an array of the references of the identifier
    //
    // Note that the references array also includes the identifier but it
    // does exclude the declaration.
    //
    // Example:
    //     var ctx = new esrefactor.Context('var x; x; x = 42');
    //     var id = ctx.identify(10);
    //
    //  id will have the value of:
    //      {
    //        identifier: {
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [10, 11]
    //        },
    //        declaration: {
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [4, 5]
    //        },
    //        references: [{
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [7, 8]
    //        }, {
    //          type: 'Identifier',
    //          name: 'x',
    //          range: [10, 11]
    //        }]
    //      }

    Context.prototype.identify = function (pos) {
        if (!this._syntax) {
            throw new Error('Unable to identify anything without a syntax tree');
        }
        if (!this._scopeManager) {
            throw new Error('Unable to identify anything without a valid scope manager');
        }
        var identifier = this._getIdentifierNodeByPosition(pos);
        if (!identifier) {
            return;
        }
        var scope = this._getScopeByPosition(pos);
        var result = this._lookup(scope, identifier);
        result.declaration = this._lookupDeclaration(scope, identifier);
        result.references = this._findAllReferencesInScope(result.declaration, identifier);
        return result;
    };

    // Search for all other identical references (same scope).
    Context.prototype._findAllReferencesInScope = function(declaration, identifier) {
        var scopeManager = this._scopeManager;
        var lookupDeclaration = this._lookupDeclaration;
        var references = [];
        scopeManager.attach();
        estraverse.traverse(this._syntax, {
            enter: function (node) {
                var scope, i, ref;
                scope = scopeManager.acquire(node);
                for (i = 0; i < (scope ? scope.references.length : 0); ++i) {
                    ref = scope.references[i];
                    if (ref.identifier.name === identifier.name) {
                        if (lookupDeclaration(scope, ref.identifier) === declaration) {
                            references.push(ref.identifier);
                        }
                    }
                }
            }
        });
        scopeManager.detach();
        return references;
    };

    // Rename the identifier and its reference to a new specific name.
    // The return value is the new code after the identifier is renamed.
    //
    // This functions needs identification, which is obtain using identify() function.
    //
    // Example:
    //   var ctx = new esrefactor.Context('var x; x = 42');
    //   var id = ctx.identify(4);
    //   var code = ctx.rename(id, 'y');
    //
    // code will be `var y; y = 42'.

    Context.prototype.rename = function (identification, name) {
        var result, list, set, i, id, entry;

        if (!this._code) {
            throw new Error('Unable to rename without the original source');
        }

        result = this._code;
        if (typeof identification === 'undefined') {
            return result;
        }

        list = [identification.identifier.range];
        if (identification.declaration) {
            list.push(identification.declaration.range);
        }
        for (i = 0; i < identification.references.length; ++i) {
            list.push(identification.references[i].range);
        }

        // Sort the references based on the position to prevent
        // shifting all the ranges.
        list.sort(function (a, b) { return b[0] - a[0]; });

        // Prevent double renaming, get the unique set.
        set = [];
        set.push(list[0]);
        for (i = 1; i < list.length; ++i) {
            if (list[i][0] !== list[i - 1][0]) {
                set.push(list[i]);
            }
        }

        id = identification.identifier.name;
        for (i = 0; i < set.length; ++i) {
            result = result.slice(0, set[i][0]) + name + result.slice(set[i][1]);
        }

        return result;
    };

    exports.Context = Context;
}));
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import connectToStores from '../utils/connectToStores';
import translate from '../i18n/Translate';
import LoginActionCreators from '../actions/LoginActionCreators';
import * as TagSuggestionsActionCreators from '../actions/TagSuggestionsActionCreators';
import LocaleStore from '../stores/LocaleStore';
import ProfileStore from '../stores/ProfileStore';
import RegisterStore from '../stores/RegisterStore';
import TagSuggestionsStore from '../stores/TagSuggestionsStore';
import InputTag from '../components/RegisterFields/InputTag/InputTag.js';
import StepsBar from '../components/ui/StepsBar/StepsBar.js';
import TopNavBar from '../components/TopNavBar/TopNavBar.js';
import * as UserActionCreators from '../actions/UserActionCreators';
import '../../scss/pages/professional-profile-skills.scss';

function requestData(props) {
    if (!props.metadata) {
        UserActionCreators.requestMetadata();
    }
}

function resetTagSuggestions() {
    TagSuggestionsActionCreators.resetTagSuggestions();
}

function getState() {
    const interfaceLanguage = LocaleStore.locale;
    const tags = TagSuggestionsStore.tags || [];
    const tagValues = tags.map(tag => tag.name);
    const user = RegisterStore.user;
    const username = user && user.username ? user.username : null;
    const profile = RegisterStore.profile;

    return {
        interfaceLanguage,
        tagValues,
        profile,
        username
    };
}

@translate('ProfessionalProfileSkillsPage')
@connectToStores([LocaleStore, ProfileStore, RegisterStore, TagSuggestionsStore], getState)
export default class ProfessionalProfileSkillsPage extends Component {

    static propTypes = {
        // Injected by @translate:
        strings          : PropTypes.object,
        // Injected by @connectToStores:
        tagValues        : PropTypes.array,
        profile          : PropTypes.object,
        username         : PropTypes.string,
        interfaceLanguage: PropTypes.string
    };

    static contextTypes = {
        router: PropTypes.object.isRequired
    };

    constructor(props) {
        super(props);

        this.goToLeisureProfilePage = this.goToLeisureProfilePage.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    componentDidMount() {
        if (!this.props.username) {
            this.context.router.push('/answer-username');
        }
        resetTagSuggestions();
        requestData(this.props);
    }

    goToLeisureProfilePage() {
        this.context.router.push('/leisure-profile');
    }

    onChange(tags) {
        const {profile} = this.props;

        resetTagSuggestions();
        LoginActionCreators.preRegisterProfile({...profile, ...{profession: tags}});
    }

    onChangeText(text) {
        if (text) {
            TagSuggestionsActionCreators.requestProfileTagSuggestions(text, 'profession');
        } else {
            resetTagSuggestions();
        }
    }

    render() {
        const {tagValues, profile, strings} = this.props;
        const canContinue = profile && profile.profession && profile.profession.length > 0;

        return (

                    <TopNavBar iconLeft={'arrow-left'} textCenter={strings.workAndIdeas} textSize={'small'}/>

{strings.title}

                        <InputTag tags={tagValues}
                                  placeholder={strings.searchIndustry}
                                  searchIcon={true}
                                  size={'small'}
                                  chipsColor={'blue'}
                                  onChangeHandler={this.onChangeText}
                                  onClickHandler={this.onChange}
                                  selectedLabel={strings.selected}/>

                <StepsBar color={'blue'} canContinue={canContinue} cantContinueText={strings.addIndustry} continueText={strings.continue} currentStep={1} totalSteps={2} onClickHandler={this.goToLeisureProfilePage}/>

        );
    }

}

ProfessionalProfileSkillsPage.defaultProps = {
    strings: {
        workAndIdeas  : 'Work, Ideas & Projects',
        title         : 'What are your professional skills?',
        selected      : 'Your professional skills',
        searchIndustry: 'Search skill',
        addIndustry   : 'Add a skill to continue',
        continue      : 'Continue'
    }
};<commit_msg>QS-1563: Fix tag building on skills page<commit_after>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import connectToStores from '../utils/connectToStores';
import translate from '../i18n/Translate';
import LoginActionCreators from '../actions/LoginActionCreators';
import * as TagSuggestionsActionCreators from '../actions/TagSuggestionsActionCreators';
import LocaleStore from '../stores/LocaleStore';
import ProfileStore from '../stores/ProfileStore';
import RegisterStore from '../stores/RegisterStore';
import TagSuggestionsStore from '../stores/TagSuggestionsStore';
import InputTag from '../components/RegisterFields/InputTag/InputTag.js';
import StepsBar from '../components/ui/StepsBar/StepsBar.js';
import TopNavBar from '../components/TopNavBar/TopNavBar.js';
import * as UserActionCreators from '../actions/UserActionCreators';
import '../../scss/pages/professional-profile-skills.scss';

function requestData(props) {
    if (!props.metadata) {
        UserActionCreators.requestMetadata();
    }
}

function resetTagSuggestions() {
    TagSuggestionsActionCreators.resetTagSuggestions();
}

function getState() {
    const interfaceLanguage = LocaleStore.locale;
    const tags = TagSuggestionsStore.tags || [];
    const tagValues = tags.map(tag => tag.name);
    const user = RegisterStore.user;
    const username = user && user.username ? user.username : null;
    const profile = RegisterStore.profile;

    return {
        interfaceLanguage,
        tagValues,
        profile,
        username
    };
}

@translate('ProfessionalProfileSkillsPage')
@connectToStores([LocaleStore, ProfileStore, RegisterStore, TagSuggestionsStore], getState)
export default class ProfessionalProfileSkillsPage extends Component {

    static propTypes = {
        // Injected by @translate:
        strings          : PropTypes.object,
        // Injected by @connectToStores:
        tagValues        : PropTypes.array,
        profile          : PropTypes.object,
        username         : PropTypes.string,
        interfaceLanguage: PropTypes.string
    };

    static contextTypes = {
        router: PropTypes.object.isRequired
    };

    constructor(props) {
        super(props);

        this.goToLeisureProfilePage = this.goToLeisureProfilePage.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    componentDidMount() {
        if (!this.props.username) {
            this.context.router.push('/answer-username');
        }
        resetTagSuggestions();
        requestData(this.props);
    }

    goToLeisureProfilePage() {
        this.context.router.push('/leisure-profile');
    }

    onChange(values) {
        const {profile} = this.props;

        const tags = this.buildTags(values);
        resetTagSuggestions();
        LoginActionCreators.preRegisterProfile({...profile, ...{profession: tags}});
    }

    buildTags(strings)
    {
        return strings.map((choice) => {return {name: choice}});
    }

    onChangeText(text) {
        if (text) {
            TagSuggestionsActionCreators.requestProfileTagSuggestions(text, 'profession');
        } else {
            resetTagSuggestions();
        }
    }

    render() {
        const {tagValues, profile, strings} = this.props;
        const canContinue = profile && profile.profession && profile.profession.length > 0;

        return (

                    <TopNavBar iconLeft={'arrow-left'} textCenter={strings.workAndIdeas} textSize={'small'}/>

{strings.title}

                        <InputTag tags={tagValues}
                                  placeholder={strings.searchIndustry}
                                  searchIcon={true}
                                  size={'small'}
                                  chipsColor={'blue'}
                                  onChangeHandler={this.onChangeText}
                                  onClickHandler={this.onChange}
                                  selectedLabel={strings.selected}/>

                <StepsBar color={'blue'} canContinue={canContinue} cantContinueText={strings.addIndustry} continueText={strings.continue} currentStep={1} totalSteps={2} onClickHandler={this.goToLeisureProfilePage}/>

        );
    }

}

ProfessionalProfileSkillsPage.defaultProps = {
    strings: {
        workAndIdeas  : 'Work, Ideas & Projects',
        title         : 'What are your professional skills?',
        selected      : 'Your professional skills',
        searchIndustry: 'Search skill',
        addIndustry   : 'Add a skill to continue',
        continue      : 'Continue'
    }
};<|endoftext|>


<commit_before>var RoboHydraException = function(name, message) {
    Error.call(this, message);
    this.name = name;
};
RoboHydraException.prototype = new Error();
RoboHydraException.prototype.toString = function() {
    return this.name;
};

var InvalidRoboHydraResponseException = function() {
    RoboHydraException.call(this,
                            "InvalidRoboHydraResponseException",
                            'No "end" event handler for this response object');
};
InvalidRoboHydraResponseException.prototype = new RoboHydraException();

var InvalidRoboHydraResponseEventException = function(eventName) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraResponseEventException",
                            'There\'s no "' + eventName + '" event');
};
InvalidRoboHydraResponseEventException.prototype = new RoboHydraException();

var InvalidRoboHydraRequestException = function(propName, propValue) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraRequestException",
                            'Invalid request: property "' + propName +
                                '" had value "' + propValue + '"');
};
InvalidRoboHydraRequestException.prototype = new RoboHydraException();

var InvalidRoboHydraHeadException = function(message) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraHeadException",
                            message || "Invalid or incomplete RoboHydra Head");
};
InvalidRoboHydraHeadException.prototype = new RoboHydraException();

var InvalidRoboHydraHeadStateException = function(attachedState) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraHeadStateException",
                            "Can't execute that operation when the hydra " +
                                "head is " +
                                attachedState ? "attached" : "detached");
};
InvalidRoboHydraHeadStateException.prototype = new RoboHydraException();

var InvalidRoboHydraPluginException = function() {
    RoboHydraException.call(this,
                            "InvalidRoboHydraPluginException",
                            "Invalid or incomplete plugin");
};
InvalidRoboHydraPluginException.prototype = new RoboHydraException();

var DuplicateRoboHydraPluginException = function() {
    RoboHydraException.call(this,
                            "DuplicateRoboHydraPluginException",
                            "Duplicate or incomplete plugin");
};
DuplicateRoboHydraPluginException.prototype = new RoboHydraException();

var DuplicateRoboHydraHeadNameException = function(head) {
    RoboHydraException.call(this,
                            "DuplicateRoboHydraHeadNameException",
                            'Duplicate RoboHydra head name "' + head + '"');
};
DuplicateRoboHydraHeadNameException.prototype = new RoboHydraException();

var RoboHydraHeadNotFoundException = function(pluginName, headName) {
    RoboHydraException.call(this,
                            "RoboHydraHeadNotFoundException",
                            'Could not find the given head ("' +
                                pluginName + '" / "' + headName + '")');
};
RoboHydraHeadNotFoundException.prototype = new RoboHydraException();

var RoboHydraPluginNotFoundException = function(pluginName) {
    RoboHydraException.call(this,
                            "RoboHydraPluginNotFoundException",
                            'Could not find the given plugin ("' +
                                pluginName + '")');
};
RoboHydraPluginNotFoundException.prototype = new RoboHydraException();

var InvalidRoboHydraTestException = function(pluginName, testName) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraTestException",
                            'Could not find the given test ("' +
                                pluginName + '" / "' + testName + '")');
};
InvalidRoboHydraTestException.prototype = new RoboHydraException();

var InvalidRoboHydraNextParametersException = function(plugin, test,  args) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraNextParametersException",
                            'Invalid parameters passed to the next ' +
                                'function "' + plugin.name + '" / "' +
                                test.name + '". Expected (req, res), ' +
                                'received ' + args.length + ' parameters: (' +
                                Array.prototype.join.call(args, ", ") + ')');
};
InvalidRoboHydraNextParametersException.prototype = new RoboHydraException();

exports.RoboHydraException = RoboHydraException;
exports.InvalidRoboHydraResponseEventException =
    InvalidRoboHydraResponseEventException;
exports.InvalidRoboHydraResponseException = InvalidRoboHydraResponseException;
exports.InvalidRoboHydraHeadException  = InvalidRoboHydraHeadException;
exports.InvalidRoboHydraHeadStateException  = InvalidRoboHydraHeadStateException;
exports.InvalidRoboHydraPluginException = InvalidRoboHydraPluginException;
exports.DuplicateRoboHydraPluginException = DuplicateRoboHydraPluginException;
exports.DuplicateRoboHydraHeadNameException = DuplicateRoboHydraHeadNameException;
exports.RoboHydraHeadNotFoundException = RoboHydraHeadNotFoundException;
exports.RoboHydraPluginNotFoundException = RoboHydraPluginNotFoundException;
exports.InvalidRoboHydraTestException = InvalidRoboHydraTestException;
exports.InvalidRoboHydraNextParametersException = InvalidRoboHydraNextParametersException;
<commit_msg>Better exceptions<commit_after>function RoboHydraException(name, message) {
    // I have no fucking clue why, but this seems to be the only way
    // to get a sane exception thrown, with stack track and all
    var e = new Error(message);
    e.name = name;
    this.stack = e.stack;
    this.name = name;
    this.message = message;
}

function InvalidRoboHydraResponseException() {
    RoboHydraException.call(this,
                            "InvalidRoboHydraResponseException",
                            'No "end" event handler for this response object');
}

var InvalidRoboHydraResponseEventException = function(eventName) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraResponseEventException",
                            'There\'s no "' + eventName + '" event');
};

var InvalidRoboHydraRequestException = function(propName, propValue) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraRequestException",
                            'Invalid request: property "' + propName +
                                '" had value "' + propValue + '"');
};

var InvalidRoboHydraHeadException = function(message) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraHeadException",
                            message || "Invalid or incomplete RoboHydra Head");
};

var InvalidRoboHydraHeadStateException = function(attachedState) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraHeadStateException",
                            "Can't execute that operation when the hydra " +
                                "head is " +
                                attachedState ? "attached" : "detached");
};

var InvalidRoboHydraPluginException = function() {
    RoboHydraException.call(this,
                            "InvalidRoboHydraPluginException",
                            "Invalid or incomplete plugin");
};

var DuplicateRoboHydraPluginException = function() {
    RoboHydraException.call(this,
                            "DuplicateRoboHydraPluginException",
                            "Duplicate or incomplete plugin");
};

var DuplicateRoboHydraHeadNameException = function(head) {
    RoboHydraException.call(this,
                            "DuplicateRoboHydraHeadNameException",
                            'Duplicate RoboHydra head name "' + head + '"');
};

var RoboHydraHeadNotFoundException = function(pluginName, headName) {
    RoboHydraException.call(this,
                            "RoboHydraHeadNotFoundException",
                            'Could not find the given head ("' +
                                pluginName + '" / "' + headName + '")');
};

var RoboHydraPluginNotFoundException = function(pluginName) {
    RoboHydraException.call(this,
                            "RoboHydraPluginNotFoundException",
                            'Could not find the given plugin ("' +
                                pluginName + '")');
};

var InvalidRoboHydraTestException = function(pluginName, testName) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraTestException",
                            'Could not find the given test ("' +
                                pluginName + '" / "' + testName + '")');
};

var InvalidRoboHydraNextParametersException = function(plugin, test,  args) {
    RoboHydraException.call(this,
                            "InvalidRoboHydraNextParametersException",
                            'Invalid parameters passed to the next ' +
                                'function "' + plugin.name + '" / "' +
                                test.name + '". Expected (req, res), ' +
                                'received ' + args.length + ' parameters: (' +
                                Array.prototype.join.call(args, ", ") + ')');
};

exports.RoboHydraException = RoboHydraException;
exports.InvalidRoboHydraResponseEventException =
    InvalidRoboHydraResponseEventException;
exports.InvalidRoboHydraResponseException = InvalidRoboHydraResponseException;
exports.InvalidRoboHydraHeadException  = InvalidRoboHydraHeadException;
exports.InvalidRoboHydraHeadStateException  = InvalidRoboHydraHeadStateException;
exports.InvalidRoboHydraPluginException = InvalidRoboHydraPluginException;
exports.DuplicateRoboHydraPluginException = DuplicateRoboHydraPluginException;
exports.DuplicateRoboHydraHeadNameException = DuplicateRoboHydraHeadNameException;
exports.RoboHydraHeadNotFoundException = RoboHydraHeadNotFoundException;
exports.RoboHydraPluginNotFoundException = RoboHydraPluginNotFoundException;
exports.InvalidRoboHydraTestException = InvalidRoboHydraTestException;
exports.InvalidRoboHydraNextParametersException = InvalidRoboHydraNextParametersException;
<|endoftext|>


<commit_before>const util = require('util')
const request = util.promisify(require('request'))
const randomstring = require('randomstring')

module.exports = async (Joi) => {
  const useHttps = (process.env.USE_HTTPS)
    ? (process.env.USE_HTTPS.toLowerCase() !== 'false')
    : true

  const clientId = (process.env.KIBANA_OAUTH2_CLIENT_ID)
    ? process.env.KIBANA_OAUTH2_CLIENT_ID
    : 'client_id'

  const clientSecret = (process.env.KIBANA_OAUTH2_CLIENT_SECRET)
    ? process.env.KIBANA_OAUTH2_CLIENT_SECRET
    : 'client_secret'

  const skipSslValidation = (process.env.SKIP_SSL_VALIDATION)
    ? (process.env.SKIP_SSL_VALIDATION.toLowerCase() === 'true')
    : false

  const cfSystemOrg = (process.env.CF_SYSTEM_ORG) ? process.env.CF_SYSTEM_ORG : 'system'

  // TODO remove default value for CF_API_URI
  const cloudFoundryApiUri = (process.env.CF_API_URI)
    ? process.env.CF_API_URI.replace(/\/$/, '')
    : 'https://api.cf03.mydeploy.xyz'

  const logoutRedirectUri = (process.env.KIBANA_DOMAIN)
    ? ((useHttps)
      ? 'https://' : 'http://') + (process.env.KIBANA_DOMAIN ? process.env.KIBANA_DOMAIN : 'localhost:5601') + '/login' : ''

  const useRedisSessions = (process.env.REDIS_HOST) ? true : false
  const redisHost = (process.env.REDIS_HOST) ? process.env.REDIS_HOST : '127.0.0.1'
  const redisPort = (process.env.REDIS_PORT) ? process.env.REDIS_PORT : '6379'
  const cfInfoUri = cloudFoundryApiUri + '/v2/info'
  const sessionExpirationMs = (process.env.SESSION_EXPIRATION_MS)
    ? process.env.SESSION_EXPIRATION_MS
    : 12 * 60 * 60 * 1000 // 12 hours by default
  const randomString = process.env.SESSION_KEY || randomstring.generate(40)
  const skipAuthorization = process.env.SKIP_AUTHORIZATION

  if (skipSslValidation) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
  }
  try {
    // Fetch location of login server, then set config
    const response = await request(cfInfoUri)

    const cfInfo = JSON.parse(response.body)
    const result = Joi.object({
      enabled: Joi.boolean().default(true),
      client_id: Joi.string().default(clientId),
      client_secret: Joi.string().default(clientSecret),
      skip_ssl_validation: Joi.boolean().default(skipSslValidation),
      cf_system_org: Joi.string().default(cfSystemOrg),
      authorization_uri: Joi.string().default(cfInfo.authorization_endpoint + '/oauth/authorize'),
      logout_uri: Joi.string().default(cfInfo.authorization_endpoint + '/logout.do' +
        /*
          Set 'redirect' parameter
          if logout_redirect_uri property is set to get back to Kibana app after logout.
          (note that redirects after logout
          should be also enabled
          in UAA - e.g. https://github.com/cloudfoundry/uaa/blob/3.9.3/uaa/src/main/resources/login.yml#L38-L45)
        */
        ((logoutRedirectUri !== '') ? '?redirect=' + logoutRedirectUri : '')),
      token_uri: Joi.string().default(cfInfo.token_endpoint + '/oauth/token'),
      account_info_uri: Joi.string().default(cfInfo.token_endpoint + '/userinfo'),
      organizations_uri: Joi.string().default(cloudFoundryApiUri + '/v2/organizations'),
      spaces_uri: Joi.string().default(cloudFoundryApiUri + '/v2/spaces'),
      random_passphrase: Joi.string().default(randomString),
      use_redis_sessions: Joi.boolean().default(useRedisSessions),
      redis_host: Joi.string().default(redisHost),
      redis_port: Joi.string().default(redisPort),
      session_expiration_ms: Joi.number().integer().default(sessionExpirationMs),
      use_https: Joi.boolean().default(useHttps),
      skip_authorization: Joi.boolean().default(skipAuthorization)
    }).default()

    return result
  } catch (error) {
    console.error(`config.ERROR fetching CF info from "${cfInfoUri}`, error)

    return Joi.object({
      enabled: Joi.boolean().default(true)
    }).default()
  }
}
<commit_msg>Remove fallback value for system_domain<commit_after>const util = require('util')
const request = util.promisify(require('request'))
const randomstring = require('randomstring')

module.exports = async (Joi) => {
  const useHttps = (process.env.USE_HTTPS)
    ? (process.env.USE_HTTPS.toLowerCase() !== 'false')
    : true

  const clientId = (process.env.KIBANA_OAUTH2_CLIENT_ID)
    ? process.env.KIBANA_OAUTH2_CLIENT_ID
    : 'client_id'

  const clientSecret = (process.env.KIBANA_OAUTH2_CLIENT_SECRET)
    ? process.env.KIBANA_OAUTH2_CLIENT_SECRET
    : 'client_secret'

  const skipSslValidation = (process.env.SKIP_SSL_VALIDATION)
    ? (process.env.SKIP_SSL_VALIDATION.toLowerCase() === 'true')
    : false

  const cfSystemOrg = (process.env.CF_SYSTEM_ORG) ? process.env.CF_SYSTEM_ORG : 'system'

  if (!process.env.CF_API_URI) {
    throw new Error(`config.ERROR system_domain is missing in kibana-auth-plugin.yml`);
  }

  const cloudFoundryApiUri = process.env.CF_API_URI.replace(/\/$/, "");

  const logoutRedirectUri = (process.env.KIBANA_DOMAIN)
    ? ((useHttps)
      ? 'https://' : 'http://') + (process.env.KIBANA_DOMAIN ? process.env.KIBANA_DOMAIN : 'localhost:5601') + '/login' : ''

  const useRedisSessions = (process.env.REDIS_HOST) ? true : false
  const redisHost = (process.env.REDIS_HOST) ? process.env.REDIS_HOST : '127.0.0.1'
  const redisPort = (process.env.REDIS_PORT) ? process.env.REDIS_PORT : '6379'
  const cfInfoUri = cloudFoundryApiUri + '/v2/info'
  const sessionExpirationMs = (process.env.SESSION_EXPIRATION_MS)
    ? process.env.SESSION_EXPIRATION_MS
    : 12 * 60 * 60 * 1000 // 12 hours by default
  const randomString = process.env.SESSION_KEY || randomstring.generate(40)
  const skipAuthorization = process.env.SKIP_AUTHORIZATION

  if (skipSslValidation) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
  }
  try {
    // Fetch location of login server, then set config
    const response = await request(cfInfoUri)

    const cfInfo = JSON.parse(response.body)
    const result = Joi.object({
      enabled: Joi.boolean().default(true),
      client_id: Joi.string().default(clientId),
      client_secret: Joi.string().default(clientSecret),
      skip_ssl_validation: Joi.boolean().default(skipSslValidation),
      cf_system_org: Joi.string().default(cfSystemOrg),
      authorization_uri: Joi.string().default(cfInfo.authorization_endpoint + '/oauth/authorize'),
      logout_uri: Joi.string().default(cfInfo.authorization_endpoint + '/logout.do' +
        /*
          Set 'redirect' parameter
          if logout_redirect_uri property is set to get back to Kibana app after logout.
          (note that redirects after logout
          should be also enabled
          in UAA - e.g. https://github.com/cloudfoundry/uaa/blob/3.9.3/uaa/src/main/resources/login.yml#L38-L45)
        */
        ((logoutRedirectUri !== '') ? '?redirect=' + logoutRedirectUri : '')),
      token_uri: Joi.string().default(cfInfo.token_endpoint + '/oauth/token'),
      account_info_uri: Joi.string().default(cfInfo.token_endpoint + '/userinfo'),
      organizations_uri: Joi.string().default(cloudFoundryApiUri + '/v2/organizations'),
      spaces_uri: Joi.string().default(cloudFoundryApiUri + '/v2/spaces'),
      random_passphrase: Joi.string().default(randomString),
      use_redis_sessions: Joi.boolean().default(useRedisSessions),
      redis_host: Joi.string().default(redisHost),
      redis_port: Joi.string().default(redisPort),
      session_expiration_ms: Joi.number().integer().default(sessionExpirationMs),
      use_https: Joi.boolean().default(useHttps),
      skip_authorization: Joi.boolean().default(skipAuthorization)
    }).default()

    return result
  } catch (error) {
    console.error(`config.ERROR fetching CF info from "${cfInfoUri}`, error)

    return Joi.object({
      enabled: Joi.boolean().default(true)
    }).default()
  }
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var async = require('async');
var log = require('npmlog');

var express = require('express');
var querystring = require('querystring');
var bodyParser = require('body-parser')

var WalletService = require('./server');
var Stats = require('./stats');

log.disableColor();
log.debug = log.verbose;
log.level = 'debug';

var ExpressApp = function() {
  this.app = express();
};

/**
 * start
 *
 * @param opts.WalletService options for WalletService class
 * @param opts.basePath
 * @param opts.disableLogs
 * @param {Callback} cb
 */
ExpressApp.prototype.start = function(opts, cb) {
  opts = opts || {};

  this.app.use(function(req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'x-signature,x-identity,x-client-version,X-Requested-With,Content-Type,Authorization');
    next();
  });
  var allowCORS = function(req, res, next) {
    if ('OPTIONS' == req.method) {
      res.sendStatus(200);
      res.end();
      return;
    }
    next();
  }
  this.app.use(allowCORS);
  this.app.enable('trust proxy');

  var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ;

  this.app.use(bodyParser.json({
    limit: POST_LIMIT
  }));

  if (opts.disableLogs) {
    log.level = 'silent';
  } else {
    // TODO access.log
    //var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'})
    //app.use(morgan('combined', {stream: accessLogStream}))
    // app.use(require('morgan')('dev'));
    this.app.use(require('morgan')(':remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" '));
  }

  var router = express.Router();

  function returnError(err, res, req) {
    if (err instanceof WalletService.ClientError) {

      var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
      if (!opts.disableLogs)
        log.info('Client Err: ' + status + ' ' + req.url + ' ' + err);

      res.status(status).json({
        code: err.code,
        message: err.message,
      }).end();
    } else {
      var code, message;
      if (_.isObject(err)) {
        code = err.code;
        message = err.message;
      }
      var m = message || err.toString();

      if (!opts.disableLogs)
        log.error('Err: ' + req.url + ' :' + code + ':' + m);

      res.status(code || 500).json({
        error: m,
      }).end();
    }
  };

  function getCredentials(req) {
    var identity = req.header('x-identity');
    if (!identity) return;

    return {
      copayerId: identity,
      signature: req.header('x-signature'),
    };
  };

  function getServer(req, res, cb) {
    var opts = {
      clientVersion: req.header('x-client-version'),
    };
    return WalletService.getInstance(opts);
  };

  function getServerWithAuth(req, res, cb) {
    var credentials = getCredentials(req);
    if (!credentials)
      return returnError(new WalletService.ClientError({
        code: 'NOT_AUTHORIZED'
      }), res, req);

    var auth = {
      copayerId: credentials.copayerId,
      message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
      signature: credentials.signature,
      clientVersion: req.header('x-client-version'),
    };
    WalletService.getInstanceWithAuth(auth, function(err, server) {
      if (err) return returnError(err, res, req);
      return cb(server);
    });
  };

  router.post('/v1/wallets/', function(req, res) {
    var server = getServer(req, res);
    server.createWallet(req.body, function(err, walletId) {
      if (err) return returnError(err, res, req);
      res.json({
        walletId: walletId,
      });
    });
  });

  router.put('/v1/copayers/', function(req, res) {
    var server = getServer(req, res);
    server.addAccess(req.body, function(err, result) {
      if (err) return returnError(err, res, req);
      res.json(result);
    });
  });

  router.post('/v1/wallets/:id/copayers/', function(req, res) {
    req.body.walletId = req.params['id'];
    var server = getServer(req, res);
    server.joinWallet(req.body, function(err, result) {
      if (err) return returnError(err, res, req);

      res.json(result);
    });
  });

  router.get('/v1/wallets/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      var result = {};
      async.parallel([

        function(next) {
          server.getWallet({}, function(err, wallet) {
            if (err) return next(err);
            result.wallet = wallet;
            next();
          });
        },
        function(next) {
          server.getBalance({}, function(err, balance) {
            if (err) return next(err);
            result.balance = balance;
            next();
          });
        },
        function(next) {
          server.getPendingTxs({}, function(err, pendingTxps) {
            if (err) return next(err);
            result.pendingTxps = pendingTxps;
            next();
          });
        },
        function(next) {
          server.getPreferences({}, function(err, preferences) {
            if (err) return next(err);
            result.preferences = preferences;
            next();
          });
        },
      ], function(err) {
        if (err) return returnError(err, res, req);
        res.json(result);
      });
    });
  });

  router.get('/v1/preferences/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getPreferences({}, function(err, preferences) {
        if (err) return returnError(err, res, req);
        res.json(preferences);
      });
    });
  });

  router.put('/v1/preferences', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.savePreferences(req.body, function(err, result) {
        if (err) return returnError(err, res, req);
        res.json(result);
      });
    });
  });

  router.get('/v1/txproposals/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getPendingTxs({}, function(err, pendings) {
        if (err) return returnError(err, res, req);
        res.json(pendings);
      });
    });
  });

  router.post('/v1/txproposals/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.createTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
      });
    });
  });

  router.post('/v1/addresses/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.createAddress(req.body, function(err, address) {
        if (err) return returnError(err, res, req);
        res.json(address);
      });
    });
  });

  router.get('/v1/addresses/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getMainAddresses({}, function(err, addresses) {
        if (err) return returnError(err, res, req);
        res.json(addresses);
      });
    });
  });

  router.get('/v1/balance/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getBalance({}, function(err, balance) {
        if (err) return returnError(err, res, req);
        res.json(balance);
      });
    });
  });

  router.get('/v1/feelevels/', function(req, res) {
    var opts = {};
    if (req.query.network) opts.network = req.query.network;
    var server = getServer(req, res);
    server.getFeeLevels(opts, function(err, feeLevels) {
      if (err) return returnError(err, res, req);
      res.json(feeLevels);
    });
  });

  router.get('/v1/utxos/', function(req, res) {
    var opts = {};
    var addresses = req.query.addresses;
    if (addresses && _.isString(addresses)) opts.addresses = req.query.addresses.split(',');
    getServerWithAuth(req, res, function(server) {
      server.getUtxos(opts, function(err, utxos) {
        if (err) return returnError(err, res, req);
        res.json(utxos);
      });
    });
  });

  router.post('/v1/broadcast_raw/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.broadcastRawTx(req.body, function(err, txid) {
        if (err) return returnError(err, res, req);
        res.json(txid);
        res.end();
      });
    });
  });

  router.post('/v1/txproposals/:id/signatures/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.signTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
        res.end();
      });
    });
  });

  // TODO Check HTTP verb and URL name
  router.post('/v1/txproposals/:id/broadcast/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.broadcastTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
        res.end();
      });
    });
  });

  router.post('/v1/txproposals/:id/rejections', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.rejectTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
        res.end();
      });
    });
  });

  router.delete('/v1/txproposals/:id/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.removePendingTx(req.body, function(err) {
        if (err) return returnError(err, res, req);
        res.json({
          success: true
        });
        res.end();
      });
    });
  });

  router.get('/v1/txproposals/:id/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.getTx(req.body, function(err, tx) {
        if (err) return returnError(err, res, req);
        res.json(tx);
        res.end();
      });
    });
  });

  router.get('/v1/txhistory/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      var opts = {};
      if (req.query.skip) opts.skip = +req.query.skip;
      if (req.query.limit) opts.limit = +req.query.limit;

      server.getTxHistory(opts, function(err, txs) {
        if (err) return returnError(err, res, req);
        res.json(txs);
        res.end();
      });
    });
  });

  router.post('/v1/addresses/scan/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.startScan(req.body, function(err, started) {
        if (err) return returnError(err, res, req);
        res.json(started);
        res.end();
      });
    });
  });

  router.get('/v1/stats/', function(req, res) {
    var opts = {};
    if (req.query.network) opts.network = req.query.network;
    if (req.query.from) opts.from = req.query.from;
    if (req.query.to) opts.to = req.query.to;

    var stats = new Stats(opts);
    stats.run(function(err, data) {
      if (err) return returnError(err, res, req);
      res.json(data);
      res.end();
    });
  });

  this.app.use(opts.basePath || '/bws/api', router);

  WalletService.initialize(opts, cb);

};

module.exports = ExpressApp;
<commit_msg>change endpoint for add access<commit_after>'use strict';

var _ = require('lodash');
var async = require('async');
var log = require('npmlog');

var express = require('express');
var querystring = require('querystring');
var bodyParser = require('body-parser')

var WalletService = require('./server');
var Stats = require('./stats');

log.disableColor();
log.debug = log.verbose;
log.level = 'debug';

var ExpressApp = function() {
  this.app = express();
};

/**
 * start
 *
 * @param opts.WalletService options for WalletService class
 * @param opts.basePath
 * @param opts.disableLogs
 * @param {Callback} cb
 */
ExpressApp.prototype.start = function(opts, cb) {
  opts = opts || {};

  this.app.use(function(req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'x-signature,x-identity,x-client-version,X-Requested-With,Content-Type,Authorization');
    next();
  });
  var allowCORS = function(req, res, next) {
    if ('OPTIONS' == req.method) {
      res.sendStatus(200);
      res.end();
      return;
    }
    next();
  }
  this.app.use(allowCORS);
  this.app.enable('trust proxy');

  var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ;

  this.app.use(bodyParser.json({
    limit: POST_LIMIT
  }));

  if (opts.disableLogs) {
    log.level = 'silent';
  } else {
    // TODO access.log
    //var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'})
    //app.use(morgan('combined', {stream: accessLogStream}))
    // app.use(require('morgan')('dev'));
    this.app.use(require('morgan')(':remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" '));
  }

  var router = express.Router();

  function returnError(err, res, req) {
    if (err instanceof WalletService.ClientError) {

      var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
      if (!opts.disableLogs)
        log.info('Client Err: ' + status + ' ' + req.url + ' ' + err);

      res.status(status).json({
        code: err.code,
        message: err.message,
      }).end();
    } else {
      var code, message;
      if (_.isObject(err)) {
        code = err.code;
        message = err.message;
      }
      var m = message || err.toString();

      if (!opts.disableLogs)
        log.error('Err: ' + req.url + ' :' + code + ':' + m);

      res.status(code || 500).json({
        error: m,
      }).end();
    }
  };

  function getCredentials(req) {
    var identity = req.header('x-identity');
    if (!identity) return;

    return {
      copayerId: identity,
      signature: req.header('x-signature'),
    };
  };

  function getServer(req, res, cb) {
    var opts = {
      clientVersion: req.header('x-client-version'),
    };
    return WalletService.getInstance(opts);
  };

  function getServerWithAuth(req, res, cb) {
    var credentials = getCredentials(req);
    if (!credentials)
      return returnError(new WalletService.ClientError({
        code: 'NOT_AUTHORIZED'
      }), res, req);

    var auth = {
      copayerId: credentials.copayerId,
      message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
      signature: credentials.signature,
      clientVersion: req.header('x-client-version'),
    };
    WalletService.getInstanceWithAuth(auth, function(err, server) {
      if (err) return returnError(err, res, req);
      return cb(server);
    });
  };

  router.post('/v1/wallets/', function(req, res) {
    var server = getServer(req, res);
    server.createWallet(req.body, function(err, walletId) {
      if (err) return returnError(err, res, req);
      res.json({
        walletId: walletId,
      });
    });
  });

  router.put('/v1/copayers/:id/', function(req, res) {
    req.body.copayerId = req.params['id'];
    var server = getServer(req, res);
    server.addAccess(req.body, function(err, result) {
      if (err) return returnError(err, res, req);
      res.json(result);
    });
  });

  router.post('/v1/wallets/:id/copayers/', function(req, res) {
    req.body.walletId = req.params['id'];
    var server = getServer(req, res);
    server.joinWallet(req.body, function(err, result) {
      if (err) return returnError(err, res, req);

      res.json(result);
    });
  });

  router.get('/v1/wallets/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      var result = {};
      async.parallel([

        function(next) {
          server.getWallet({}, function(err, wallet) {
            if (err) return next(err);
            result.wallet = wallet;
            next();
          });
        },
        function(next) {
          server.getBalance({}, function(err, balance) {
            if (err) return next(err);
            result.balance = balance;
            next();
          });
        },
        function(next) {
          server.getPendingTxs({}, function(err, pendingTxps) {
            if (err) return next(err);
            result.pendingTxps = pendingTxps;
            next();
          });
        },
        function(next) {
          server.getPreferences({}, function(err, preferences) {
            if (err) return next(err);
            result.preferences = preferences;
            next();
          });
        },
      ], function(err) {
        if (err) return returnError(err, res, req);
        res.json(result);
      });
    });
  });

  router.get('/v1/preferences/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getPreferences({}, function(err, preferences) {
        if (err) return returnError(err, res, req);
        res.json(preferences);
      });
    });
  });

  router.put('/v1/preferences', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.savePreferences(req.body, function(err, result) {
        if (err) return returnError(err, res, req);
        res.json(result);
      });
    });
  });

  router.get('/v1/txproposals/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getPendingTxs({}, function(err, pendings) {
        if (err) return returnError(err, res, req);
        res.json(pendings);
      });
    });
  });

  router.post('/v1/txproposals/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.createTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
      });
    });
  });

  router.post('/v1/addresses/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.createAddress(req.body, function(err, address) {
        if (err) return returnError(err, res, req);
        res.json(address);
      });
    });
  });

  router.get('/v1/addresses/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getMainAddresses({}, function(err, addresses) {
        if (err) return returnError(err, res, req);
        res.json(addresses);
      });
    });
  });

  router.get('/v1/balance/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.getBalance({}, function(err, balance) {
        if (err) return returnError(err, res, req);
        res.json(balance);
      });
    });
  });

  router.get('/v1/feelevels/', function(req, res) {
    var opts = {};
    if (req.query.network) opts.network = req.query.network;
    var server = getServer(req, res);
    server.getFeeLevels(opts, function(err, feeLevels) {
      if (err) return returnError(err, res, req);
      res.json(feeLevels);
    });
  });

  router.get('/v1/utxos/', function(req, res) {
    var opts = {};
    var addresses = req.query.addresses;
    if (addresses && _.isString(addresses)) opts.addresses = req.query.addresses.split(',');
    getServerWithAuth(req, res, function(server) {
      server.getUtxos(opts, function(err, utxos) {
        if (err) return returnError(err, res, req);
        res.json(utxos);
      });
    });
  });

  router.post('/v1/broadcast_raw/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.broadcastRawTx(req.body, function(err, txid) {
        if (err) return returnError(err, res, req);
        res.json(txid);
        res.end();
      });
    });
  });

  router.post('/v1/txproposals/:id/signatures/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.signTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
        res.end();
      });
    });
  });

  // TODO Check HTTP verb and URL name
  router.post('/v1/txproposals/:id/broadcast/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.broadcastTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
        res.end();
      });
    });
  });

  router.post('/v1/txproposals/:id/rejections', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.rejectTx(req.body, function(err, txp) {
        if (err) return returnError(err, res, req);
        res.json(txp);
        res.end();
      });
    });
  });

  router.delete('/v1/txproposals/:id/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.removePendingTx(req.body, function(err) {
        if (err) return returnError(err, res, req);
        res.json({
          success: true
        });
        res.end();
      });
    });
  });

  router.get('/v1/txproposals/:id/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      req.body.txProposalId = req.params['id'];
      server.getTx(req.body, function(err, tx) {
        if (err) return returnError(err, res, req);
        res.json(tx);
        res.end();
      });
    });
  });

  router.get('/v1/txhistory/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      var opts = {};
      if (req.query.skip) opts.skip = +req.query.skip;
      if (req.query.limit) opts.limit = +req.query.limit;

      server.getTxHistory(opts, function(err, txs) {
        if (err) return returnError(err, res, req);
        res.json(txs);
        res.end();
      });
    });
  });

  router.post('/v1/addresses/scan/', function(req, res) {
    getServerWithAuth(req, res, function(server) {
      server.startScan(req.body, function(err, started) {
        if (err) return returnError(err, res, req);
        res.json(started);
        res.end();
      });
    });
  });

  router.get('/v1/stats/', function(req, res) {
    var opts = {};
    if (req.query.network) opts.network = req.query.network;
    if (req.query.from) opts.from = req.query.from;
    if (req.query.to) opts.to = req.query.to;

    var stats = new Stats(opts);
    stats.run(function(err, data) {
      if (err) return returnError(err, res, req);
      res.json(data);
      res.end();
    });
  });

  this.app.use(opts.basePath || '/bws/api', router);

  WalletService.initialize(opts, cb);

};

module.exports = ExpressApp;
<|endoftext|>


<commit_before>'use strict';

var date = require('./date');

/**
 * Construct a new expression parser
 *
 * Options:
 *   currentDate: iterator start date
 *   endDate: iterator end date
 *
 * @constructor
 * @private
 * @param {Object} fields  Expression fields parsed values
 * @param {Object} options Parser options
 */
function CronExpression(fields, options) {
  this._options = options;
  this._currentDate = new Date(options.currentDate.toUTCString());
  this._endDate = options.endDate ? new Date(options.endDate.toUTCString()) : null;
  this._fields = {};

  // Map fields
  for (var i = 0, c = CronExpression.map.length; i < c; i++) {
    var key = CronExpression.map[i];
    this._fields[key] = fields[i];
  }
}

/**
 * Field mappings
 * @type {Array}
 */
CronExpression.map = [ 'second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek' ];

/**
 * Prefined intervals
 * @type {Object}
 */
CronExpression.predefined = {
  '@yearly': '0 0 1 1 *',
  '@monthly': '0 0 1 * *',
  '@weekly': '0 0 * * 0',
  '@daily': '0 0 * * *',
  '@hourly': '0 * * * *'
};

/**
 * Fields constraints
 * @type {Array}
 */
CronExpression.constraints = [
  [ 0, 59 ], // Second
  [ 0, 59 ], // Minute
  [ 0, 23 ], // Hour
  [ 1, 31 ], // Day of month
  [ 1, 12 ], // Month
  [ 0, 6 ] // Day of week
];

/**
 * Field aliases
 * @type {Object}
 */
CronExpression.aliases = {
  month: {
    jan: 0,
    feb: 1,
    mar: 2,
    apr: 3,
    may: 4,
    jun: 5,
    jul: 6,
    aug: 7,
    sep: 8,
    oct: 9,
    nov: 10,
    dec: 11
  },

  dayOfWeek: {
    sun: 0,
    mon: 1,
    tue: 2,
    wed: 3,
    thu: 4,
    fri: 5,
    sat: 6
  }
};

/**
 * Field defaults
 * @type {Array}
 */
CronExpression.parseDefaults = [ '0', '*', '*', '*', '*', '*' ];

/**
 * Parse input interval
 *
 * @param   {String} field       Field symbolic name
 * @param   {String} value       Field value
 * @param   {Array} constraints  Range upper and lower constraints
 * @return  {Array}              Sequence of sorted values
 * @private
 */
CronExpression._parseField = function(field, value, constraints) {
  // Replace aliases
  switch (field) {
    case 'month':
    case 'dayOfWeek':
      var aliases = CronExpression.aliases[field];

      value = value.replace(/^[a-z]{1,3}$/gi, function(match) {
        if (aliases[match]) {
          return aliases[match];
        } else {
          throw new Error('Cannot resolve alias "' + match + '"')
        }
      });
      break;
  }

  //Check for valid characters.
  if(!CronExpression._validateCharacters(value)){
    throw new Error('Invalid characters, got value: ' + value)
  }

  // Replace '*'
  if (value.indexOf('*') !== -1) {
    value = value.replace(/\*/g, constraints[0] + '-' + constraints[1]);
  }

  //
  // Inline parsing functions
  //
  // Parser path:
  //  - parseSequence
  //    - parseRepeat
  //      - parseRange

  /**
   * Parse sequence
   *
   * @param  {String} val
   * @return {Array}
   * @private
   */
  function parseSequence(val) {
    var stack = [];

    function handleResult(result) {
      var max = stack.length > 0 ? Math.max.apply(Math, stack) : -1;

      if (result instanceof Array) { // Make sequence linear
        result.forEach(function(value, index) {
          // Check constraints
          if (!CronExpression._validateConstraint(value, constraints)) {
            throw new Error(
              'Constraint error, got value ' + value + ' expected range ' +
              constraints[0] + '-' + constraints[1]
            );
          }

          if (value > max) {
            stack.push(value);
          }

          max = Math.max.apply(Math, stack);
        })
      } else { // Scalar value
        result = parseInt(result, 10);

        // Check constraints
        if (!CronExpression._validateConstraint(result, constraints)) {
          throw new Error(
            'Constraint error, got value ' + result + ' expected range ' +
            constraints[0] + '-' + constraints[1]
          );
        }

        if (result > max) {
          stack.push(result);
        }
      }
    }

    if (val.indexOf(',') !== -1) {
      var atoms = val.split(',');

      atoms.forEach(function(value, index) {
        handleResult(parseRepeat(value.toString()));
      });

    } else {
      handleResult(parseRepeat(val));
    }

    return stack;
  }

  /**
   * Parse repetition interval
   *
   * @param  {String} val
   * @return {Array}
   */
  function parseRepeat(val) {
    var repeatInterval = 1;

    if (val.indexOf('/') !== -1) {
      var atoms = val.split('/');
      repeatInterval = atoms[atoms.length - 1];

      return parseRange(atoms[0], repeatInterval);
    } else {
      return parseRange(val, repeatInterval);
    }
  }

  /**
   * Parse range
   *
   * @param  {String} val
   * @param  {Number} repeatInterval Repetition interval
   * @return {Array}
   * @private
   */
  function parseRange(val, repeatInterval) {
    var stack = [];

    if (val.indexOf('-') !== -1) {
      var atoms = val.split('-');

      // Validate format
      if (atoms.length != 2) {
        throw new Error('Invalid range format: ' + val);
      }

      // Validate range
      var min = parseInt(atoms[0], 10);
      var max = parseInt(atoms[1], 10);

      if (Number.isNaN(min) || Number.isNaN(max) ||
          min < constraints[0] || max > constraints[1]) {
        throw new Error(
          'Constraint error, got range ' +
          min + '-' + max +
          ' expected range ' +
          constraints[0] + '-' + constraints[1]
        );
      } else if (min >= max) {
        throw new Error('Invalid range: ' + val);
      }

      // Create range
      var repeatIndex = repeatInterval;

      for (var index = min, count = max; index <= count; index++) {
        if (repeatIndex > 0 && (repeatIndex % repeatInterval) === 0) {
          repeatIndex = 1;
          stack.push(index);
        } else {
          repeatIndex++;
        }
      }

      return stack;
    } else {
      return val;
    }
  }

  return parseSequence(value);
};

CronExpression._validateCharacters = function(value) {
  var regex = new RegExp('^[\\d|/|*|\\-|,]+$');
  return regex.test(value);
};

/**
 * Constraint validation
 *
 * @private
 * @static
 * @param  {Object} value     Value to check
 * @return {Boolean}          True if validation succeeds, false if not
 */
CronExpression._validateConstraint = function(value, constraints) {
  if (value < constraints[0] || value > constraints[1]) {
    return false;
  }

  return true;
};

/**
 * Timespan validation
 *
 * @private
 * @static
 * @param  {Date} current Current date
 * @param  {Date} end     End date
 * @return {Boolean}         Return true if timespan is still valid, otherwise return false
 */
CronExpression._validateTimespan = function(current, end) {
  if (end && (end.getTime() - current.getTime()) < 0) {
    return false;
  }

  return true;
};

/**
 * Find next matching schedule date
 *
 * @return  {Number}
 * @private
 */
CronExpression.prototype._findSchedule = function() {
  // Validate timespan
  if (!CronExpression._validateTimespan(this._currentDate, this._endDate)) {
    throw new Error('Out of the timespan range');
  }

  var current = new Date(this._currentDate.toUTCString());

  function findNearest(value, sequence) {
    for (var i = 0, c = sequence.length; i < c; i++) {
      if (sequence[i] >= value) {
        return sequence[i];
      }
    }

    return sequence[0];
  }

  /**
   * Match field value
   *
   * @param  {String} value
   * @param  {Array} sequence
   * @return {Boolean}
   * @private
   */
  function matchSchedule(value, sequence) {
    return findNearest(value, sequence) === value;
  }

  // Reset
  if (this._fields.second.length === 1 && this._fields.second[0] === 0) {
    current.setSeconds(0);
    current.addMinute();
  } else {
    current.addSecond();
  }

  // Iterate and match schedule
  while (true) {
    // Validate timespan
    if (!CronExpression._validateTimespan(current, this._endDate)) {
      throw new Error('Out of the timespan range');
    }

    // Match month
    if (!matchSchedule(current.getMonth() + 1, this._fields.month)) {
      current.addMonth();
      current.setDate(1);
      current.setHours(0);
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match day of month
    if (!matchSchedule(current.getDate(), this._fields.dayOfMonth)) {
      current.addDay();
      current.setHours(0);
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match day of week
    if (!matchSchedule(current.getDay(), this._fields.dayOfWeek)) {
      current.addDay();
      current.setHours(0);
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match hour
    if (!matchSchedule(current.getHours(), this._fields.hour)) {
      current.addHour();
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match minute
    if (!matchSchedule(current.getMinutes(), this._fields.minute)) {
      current.addMinute();
      current.setSeconds(0);
      continue;
    }

    // Match second
    if (!matchSchedule(current.getSeconds(), this._fields.second)) {
      current.addSecond();
      continue;
    }

    break;
  }

  return (this._currentDate = current);
};

/**
 * Find next suitable date
 *
 * @public
 * @return {Date}
 */
CronExpression.prototype.next = function() {
  return this._findSchedule();
};

/**
 * Iterate over expression iterator
 *
 * @public
 * @param  {Number}   n        Numbers of steps to iterate
 * @param  {Function} callback Optional callback
 * @return {Array}            Array of the iterated results
 */
CronExpression.prototype.iterate = function(n, callback) {
  var dates = [];

  for (var i = 0, c = n; i < c; i++) {
    try {
      var item = this.next();
      dates.push(item);

      // Fire the callback
      if (callback) {
        callback(item, i);
      }
    } catch (e) {
      break;
    }
  }

  return dates;
};

/**
 * Reset expression iterator state
 *
 * @public
 */
CronExpression.prototype.reset = function() {
  this._currentDate = new Date(this._options.currentDate.toUTCString());
};

/**
 * Parse input expression (async)
 *
 * @public
 * @param  {String}   expression   Input expression
 * @param  {Object}   [options]  Parsing options
 * @param  {Function} callback
 */
CronExpression.parse = function(expression, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }
  try {
    var cronExpression = CronExpression.parseSync(expression, options);
    callback(null, cronExpression);
  } catch (e){
    callback(e);
  }
}

/**
 * Parse input expression (sync)
 *
 * @public
 * @param  {String}   expression   Input expression
 * @param  {Object}   [options]  Parsing options
 */
CronExpression.parseSync = function(expression, options) {
  if (!options) {
    options = {};
  }

  if (!options.currentDate) {
    options.currentDate = new Date();
  }

  // Is input expression predefined?
  if (CronExpression.predefined[expression]) {
    expression = CronExpression.predefined[expression];
  }

  // Split fields
  var fields = [];
  var atoms = expression.split(' ');

  // Resolve fields
  var start = (CronExpression.map.length - atoms.length);
  for (var i = 0, c = CronExpression.map.length; i < c; ++i) {
    var field = CronExpression.map[i]; // Field name
    var value = atoms[atoms.length > c ? i : i - start]; // Field value

    if (i < start || !value) {
      fields.push(this._parseField(field, CronExpression.parseDefaults[i], CronExpression.constraints[i]));
    } else { // Use default value
      fields.push(this._parseField(field, value, CronExpression.constraints[i]));
    }
  }

  return new CronExpression(fields, options);
};

module.exports = CronExpression;
<commit_msg>Remap month aliases<commit_after>'use strict';

var date = require('./date');

/**
 * Construct a new expression parser
 *
 * Options:
 *   currentDate: iterator start date
 *   endDate: iterator end date
 *
 * @constructor
 * @private
 * @param {Object} fields  Expression fields parsed values
 * @param {Object} options Parser options
 */
function CronExpression(fields, options) {
  this._options = options;
  this._currentDate = new Date(options.currentDate.toUTCString());
  this._endDate = options.endDate ? new Date(options.endDate.toUTCString()) : null;
  this._fields = {};

  // Map fields
  for (var i = 0, c = CronExpression.map.length; i < c; i++) {
    var key = CronExpression.map[i];
    this._fields[key] = fields[i];
  }
}

/**
 * Field mappings
 * @type {Array}
 */
CronExpression.map = [ 'second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek' ];

/**
 * Prefined intervals
 * @type {Object}
 */
CronExpression.predefined = {
  '@yearly': '0 0 1 1 *',
  '@monthly': '0 0 1 * *',
  '@weekly': '0 0 * * 0',
  '@daily': '0 0 * * *',
  '@hourly': '0 * * * *'
};

/**
 * Fields constraints
 * @type {Array}
 */
CronExpression.constraints = [
  [ 0, 59 ], // Second
  [ 0, 59 ], // Minute
  [ 0, 23 ], // Hour
  [ 1, 31 ], // Day of month
  [ 1, 12 ], // Month
  [ 0, 6 ] // Day of week
];

/**
 * Field aliases
 * @type {Object}
 */
CronExpression.aliases = {
  month: {
    jan: 1,
    feb: 2,
    mar: 3,
    apr: 4,
    may: 5,
    jun: 6,
    jul: 7,
    aug: 8,
    sep: 9,
    oct: 10,
    nov: 11,
    dec: 12
  },

  dayOfWeek: {
    sun: 0,
    mon: 1,
    tue: 2,
    wed: 3,
    thu: 4,
    fri: 5,
    sat: 6
  }
};

/**
 * Field defaults
 * @type {Array}
 */
CronExpression.parseDefaults = [ '0', '*', '*', '*', '*', '*' ];

/**
 * Parse input interval
 *
 * @param   {String} field       Field symbolic name
 * @param   {String} value       Field value
 * @param   {Array} constraints  Range upper and lower constraints
 * @return  {Array}              Sequence of sorted values
 * @private
 */
CronExpression._parseField = function(field, value, constraints) {
  // Replace aliases
  switch (field) {
    case 'month':
    case 'dayOfWeek':
      var aliases = CronExpression.aliases[field];

      value = value.replace(/^[a-z]{1,3}$/gi, function(match) {
        if (aliases[match]) {
          return aliases[match];
        } else {
          throw new Error('Cannot resolve alias "' + match + '"')
        }
      });
      break;
  }

  //Check for valid characters.
  if(!CronExpression._validateCharacters(value)){
    throw new Error('Invalid characters, got value: ' + value)
  }

  // Replace '*'
  if (value.indexOf('*') !== -1) {
    value = value.replace(/\*/g, constraints[0] + '-' + constraints[1]);
  }

  //
  // Inline parsing functions
  //
  // Parser path:
  //  - parseSequence
  //    - parseRepeat
  //      - parseRange

  /**
   * Parse sequence
   *
   * @param  {String} val
   * @return {Array}
   * @private
   */
  function parseSequence(val) {
    var stack = [];

    function handleResult(result) {
      var max = stack.length > 0 ? Math.max.apply(Math, stack) : -1;

      if (result instanceof Array) { // Make sequence linear
        result.forEach(function(value, index) {
          // Check constraints
          if (!CronExpression._validateConstraint(value, constraints)) {
            throw new Error(
              'Constraint error, got value ' + value + ' expected range ' +
              constraints[0] + '-' + constraints[1]
            );
          }

          if (value > max) {
            stack.push(value);
          }

          max = Math.max.apply(Math, stack);
        })
      } else { // Scalar value
        result = parseInt(result, 10);

        // Check constraints
        if (!CronExpression._validateConstraint(result, constraints)) {
          throw new Error(
            'Constraint error, got value ' + result + ' expected range ' +
            constraints[0] + '-' + constraints[1]
          );
        }

        if (result > max) {
          stack.push(result);
        }
      }
    }

    if (val.indexOf(',') !== -1) {
      var atoms = val.split(',');

      atoms.forEach(function(value, index) {
        handleResult(parseRepeat(value.toString()));
      });

    } else {
      handleResult(parseRepeat(val));
    }

    return stack;
  }

  /**
   * Parse repetition interval
   *
   * @param  {String} val
   * @return {Array}
   */
  function parseRepeat(val) {
    var repeatInterval = 1;

    if (val.indexOf('/') !== -1) {
      var atoms = val.split('/');
      repeatInterval = atoms[atoms.length - 1];

      return parseRange(atoms[0], repeatInterval);
    } else {
      return parseRange(val, repeatInterval);
    }
  }

  /**
   * Parse range
   *
   * @param  {String} val
   * @param  {Number} repeatInterval Repetition interval
   * @return {Array}
   * @private
   */
  function parseRange(val, repeatInterval) {
    var stack = [];

    if (val.indexOf('-') !== -1) {
      var atoms = val.split('-');

      // Validate format
      if (atoms.length != 2) {
        throw new Error('Invalid range format: ' + val);
      }

      // Validate range
      var min = parseInt(atoms[0], 10);
      var max = parseInt(atoms[1], 10);

      if (Number.isNaN(min) || Number.isNaN(max) ||
          min < constraints[0] || max > constraints[1]) {
        throw new Error(
          'Constraint error, got range ' +
          min + '-' + max +
          ' expected range ' +
          constraints[0] + '-' + constraints[1]
        );
      } else if (min >= max) {
        throw new Error('Invalid range: ' + val);
      }

      // Create range
      var repeatIndex = repeatInterval;

      for (var index = min, count = max; index <= count; index++) {
        if (repeatIndex > 0 && (repeatIndex % repeatInterval) === 0) {
          repeatIndex = 1;
          stack.push(index);
        } else {
          repeatIndex++;
        }
      }

      return stack;
    } else {
      return val;
    }
  }

  return parseSequence(value);
};

CronExpression._validateCharacters = function(value) {
  var regex = new RegExp('^[\\d|/|*|\\-|,]+$');
  return regex.test(value);
};

/**
 * Constraint validation
 *
 * @private
 * @static
 * @param  {Object} value     Value to check
 * @return {Boolean}          True if validation succeeds, false if not
 */
CronExpression._validateConstraint = function(value, constraints) {
  if (value < constraints[0] || value > constraints[1]) {
    return false;
  }

  return true;
};

/**
 * Timespan validation
 *
 * @private
 * @static
 * @param  {Date} current Current date
 * @param  {Date} end     End date
 * @return {Boolean}         Return true if timespan is still valid, otherwise return false
 */
CronExpression._validateTimespan = function(current, end) {
  if (end && (end.getTime() - current.getTime()) < 0) {
    return false;
  }

  return true;
};

/**
 * Find next matching schedule date
 *
 * @return  {Number}
 * @private
 */
CronExpression.prototype._findSchedule = function() {
  // Validate timespan
  if (!CronExpression._validateTimespan(this._currentDate, this._endDate)) {
    throw new Error('Out of the timespan range');
  }

  var current = new Date(this._currentDate.toUTCString());

  function findNearest(value, sequence) {
    for (var i = 0, c = sequence.length; i < c; i++) {
      if (sequence[i] >= value) {
        return sequence[i];
      }
    }

    return sequence[0];
  }

  /**
   * Match field value
   *
   * @param  {String} value
   * @param  {Array} sequence
   * @return {Boolean}
   * @private
   */
  function matchSchedule(value, sequence) {
    return findNearest(value, sequence) === value;
  }

  // Reset
  if (this._fields.second.length === 1 && this._fields.second[0] === 0) {
    current.setSeconds(0);
    current.addMinute();
  } else {
    current.addSecond();
  }

  // Iterate and match schedule
  while (true) {
    // Validate timespan
    if (!CronExpression._validateTimespan(current, this._endDate)) {
      throw new Error('Out of the timespan range');
    }

    // Match month
    if (!matchSchedule(current.getMonth() + 1, this._fields.month)) {
      current.addMonth();
      current.setDate(1);
      current.setHours(0);
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match day of month
    if (!matchSchedule(current.getDate(), this._fields.dayOfMonth)) {
      current.addDay();
      current.setHours(0);
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match day of week
    if (!matchSchedule(current.getDay(), this._fields.dayOfWeek)) {
      current.addDay();
      current.setHours(0);
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match hour
    if (!matchSchedule(current.getHours(), this._fields.hour)) {
      current.addHour();
      current.setMinutes(0);
      current.setSeconds(0);
      continue;
    }

    // Match minute
    if (!matchSchedule(current.getMinutes(), this._fields.minute)) {
      current.addMinute();
      current.setSeconds(0);
      continue;
    }

    // Match second
    if (!matchSchedule(current.getSeconds(), this._fields.second)) {
      current.addSecond();
      continue;
    }

    break;
  }

  return (this._currentDate = current);
};

/**
 * Find next suitable date
 *
 * @public
 * @return {Date}
 */
CronExpression.prototype.next = function() {
  return this._findSchedule();
};

/**
 * Iterate over expression iterator
 *
 * @public
 * @param  {Number}   n        Numbers of steps to iterate
 * @param  {Function} callback Optional callback
 * @return {Array}            Array of the iterated results
 */
CronExpression.prototype.iterate = function(n, callback) {
  var dates = [];

  for (var i = 0, c = n; i < c; i++) {
    try {
      var item = this.next();
      dates.push(item);

      // Fire the callback
      if (callback) {
        callback(item, i);
      }
    } catch (e) {
      break;
    }
  }

  return dates;
};

/**
 * Reset expression iterator state
 *
 * @public
 */
CronExpression.prototype.reset = function() {
  this._currentDate = new Date(this._options.currentDate.toUTCString());
};

/**
 * Parse input expression (async)
 *
 * @public
 * @param  {String}   expression   Input expression
 * @param  {Object}   [options]  Parsing options
 * @param  {Function} callback
 */
CronExpression.parse = function(expression, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }
  try {
    var cronExpression = CronExpression.parseSync(expression, options);
    callback(null, cronExpression);
  } catch (e){
    callback(e);
  }
}

/**
 * Parse input expression (sync)
 *
 * @public
 * @param  {String}   expression   Input expression
 * @param  {Object}   [options]  Parsing options
 */
CronExpression.parseSync = function(expression, options) {
  if (!options) {
    options = {};
  }

  if (!options.currentDate) {
    options.currentDate = new Date();
  }

  // Is input expression predefined?
  if (CronExpression.predefined[expression]) {
    expression = CronExpression.predefined[expression];
  }

  // Split fields
  var fields = [];
  var atoms = expression.split(' ');

  // Resolve fields
  var start = (CronExpression.map.length - atoms.length);
  for (var i = 0, c = CronExpression.map.length; i < c; ++i) {
    var field = CronExpression.map[i]; // Field name
    var value = atoms[atoms.length > c ? i : i - start]; // Field value

    if (i < start || !value) {
      fields.push(this._parseField(field, CronExpression.parseDefaults[i], CronExpression.constraints[i]));
    } else { // Use default value
      fields.push(this._parseField(field, value, CronExpression.constraints[i]));
    }
  }

  return new CronExpression(fields, options);
};

module.exports = CronExpression;
<|endoftext|>