<commit_before>import React, { PropTypes, Component } from 'react';
import * as UserActionCreators from '../../actions/UserActionCreators';
import FullWidthButton from '../ui/FullWidthButton';
import ThreadFilterList from './filters/ThreadFilterList';
import LocationFilter from './filters/LocationFilter';
import IntegerRangeFilter from './filters/IntegerRangeFilter';
import IntegerFilter from './filters/IntegerFilter';
import ChoiceFilter from './filters/ChoiceFilter';
import DoubleChoiceFilter from './filters/DoubleChoiceFilter';
import MultipleChoicesFilter from './filters/MultipleChoicesFilter';
import DoubleMultipleChoicesFilter from './filters/DoubleMultipleChoicesFilter';
import TagFilter from './filters/TagFilter';
import TagsAndChoiceFilter from './filters/TagsAndChoiceFilter';
import TagsAndMultipleChoicesFilter from './filters/TagsAndMultipleChoicesFilter';
import selectn from 'selectn';

export default class CreateUsersThread extends Component {
    static contextTypes = {
        history: PropTypes.object.isRequired
    };

    static propTypes = {
        userId: PropTypes.number.isRequired,
        filters: PropTypes.object.isRequired,
        threadName: PropTypes.string
        // TODO: defFilters should be a prop
    };

    constructor(props) {
        super(props);

        this.handleClickAddFilter = this.handleClickAddFilter.bind(this);
        this.handleClickFilterOnList = this.handleClickFilterOnList.bind(this);
        this.renderActiveFilters = this.renderActiveFilters.bind(this);
        this.handleClickFilter = this.handleClickFilter.bind(this);
        this.handleClickRemoveFilter = this.handleClickRemoveFilter.bind(this);
        this.renderLocationFilter = this.renderLocationFilter.bind(this);
        this.renderChoiceFilter = this.renderChoiceFilter.bind(this);
        this.renderDoubleChoiceFilter = this.renderDoubleChoiceFilter.bind(this);
        this.renderMultipleChoicesFilter = this.renderMultipleChoicesFilter.bind(this);
        this.renderDoubleMultipleChoicesFilter = this.renderDoubleMultipleChoicesFilter.bind(this);
        this.renderIntegerRangeFilter = this.renderIntegerRangeFilter.bind(this);
        this.renderIntegerFilter = this.renderIntegerFilter.bind(this);
        this.renderTagFilter = this.renderTagFilter.bind(this);
        this.renderTagsAndChoiceFilter = this.renderTagsAndChoiceFilter.bind(this);
        this.handleChangeFilter = this.handleChangeFilter.bind(this);
        this.handleChangeFilterAndUnSelect = this.handleChangeFilterAndUnSelect.bind(this);
        this.handleClickOutside = this.handleClickOutside.bind(this);
        this.findSelectedFilterIndex = this.findSelectedFilterIndex.bind(this);
        this.scrollToFilter = this.scrollToFilter.bind(this);
        this.createThread = this.createThread.bind(this);

        this.state = {
            selectFilter: false,
            selectedFilter: {},
            filters: []
        }
    }

    componentDidMount () {
        window.nekunoContainer.addEventListener('click', this.handleClickOutside)
    }

    componentWillUnmount () {
        window.nekunoContainer.removeEventListener('click', this.handleClickOutside)
    }

    handleClickAddFilter() {
        this.setState({
            selectFilter: true
        })
    }

    handleClickFilterOnList(checked, value) {
        let filters = this.state.filters;
        let filter = filters.find(key => key === value);
        if (typeof filter == 'undefined') {
            let defaultFilters = Object.assign({}, this.props.filters);
            filter = Object.keys(defaultFilters).map(key => defaultFilters[key]).find(function (defaultFilter) {
                return defaultFilter.key === value;
            });
        }
        if (checked) {
            filters.push(filter);
            this.setState({
                selectFilter: false,
                selectedFilter: filter,
                filters: filters
            });
            this.scrollToFilter();
        } else {
            const index = filters.findIndex(savedFilter => savedFilter.key === filter.key);
            filters.splice(index, 1);
            this.setState({
                filters: filters,
                selectedFilter: {}
            });
        }
    }

    renderActiveFilters() {
        let filters = this.state.filters;
        return (
            filters.map((filter, index) => {
                const selected = this.state.selectedFilter && this.state.selectedFilter.key === filter.key;
                switch (filter.type) {
                    case 'location_distance':
                        return this.renderLocationFilter(filter, index, selected);
                        break;
                    case 'integer_range':
                        return this.renderIntegerRangeFilter(filter, index, selected);
                        break;
                    case 'integer':
                        return this.renderIntegerFilter(filter, index, selected);
                        break;
                    case 'choice':
                        return this.renderChoiceFilter(filter, index, selected);
                        break;
                    case 'double_choice':
                        return this.renderDoubleChoiceFilter(filter, index, selected);
                        break;
                    case 'multiple_choices':
                        return this.renderMultipleChoicesFilter(filter, index, selected);
                        break;
                    case 'double_multiple_choices':
                        return this.renderDoubleMultipleChoicesFilter(filter, index, selected);
                        break;
                    case 'tags_and_choice':
                        return this.renderTagsAndChoiceFilter(filter, index, selected);
                        break;
                    case 'tags_and_multiple_choices':
                        return this.renderTagsAndMultipleChoicesFilter(filter, index, selected);
                        break;
                    case 'tags':
                        return this.renderTagFilter(filter, index, selected);
                }
            })
        );
    }

    renderLocationFilter(filter, index, selected) {
        return (
            <LocationFilter key={index} ref={selected ? 'selectedFilter' : ''}
                            filter={filter}
                            selected={selected}
                            handleClickRemoveFilter={this.handleClickRemoveFilter}
                            handleChangeFilter={this.handleChangeFilterAndUnSelect}
                            handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderIntegerRangeFilter(filter, index, selected) {
        return(
            <IntegerRangeFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                filter={filter}
                                selected={selected}
                                handleClickRemoveFilter={this.handleClickRemoveFilter}
                                handleChangeFilter={this.handleChangeFilter}
                                handleClickFilter={this.handleClickFilter}
            />
        )
    }

    renderIntegerFilter(filter, index, selected) {
        return(
            <IntegerFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                filter={filter}
                                selected={selected}
                                handleClickRemoveFilter={this.handleClickRemoveFilter}
                                handleChangeFilter={this.handleChangeFilter}
                                handleClickFilter={this.handleClickFilter}
            />
        )
    }

    renderChoiceFilter(filter, index, selected) {
        return (
            <ChoiceFilter key={index} ref={selected ? 'selectedFilter' : ''}
                          filter={filter}
                          selected={selected}
                          handleClickRemoveFilter={this.handleClickRemoveFilter}
                          handleChangeFilter={this.handleChangeFilterAndUnSelect}
                          handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderDoubleChoiceFilter(filter, index, selected) {
        return (
            <DoubleChoiceFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                filter={filter}
                                selected={selected}
                                handleClickRemoveFilter={this.handleClickRemoveFilter}
                                handleChangeFilter={this.handleChangeFilter}
                                handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderMultipleChoicesFilter(filter, index, selected) {
        return (
            <MultipleChoicesFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                   filter={filter}
                                   selected={selected}
                                   handleClickRemoveFilter={this.handleClickRemoveFilter}
                                   handleChangeFilter={this.handleChangeFilter}
                                   handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderDoubleMultipleChoicesFilter(filter, index, selected) {
        return (
            <DoubleMultipleChoicesFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                   filter={filter}
                                   selected={selected}
                                   handleClickRemoveFilter={this.handleClickRemoveFilter}
                                   handleChangeFilter={this.handleChangeFilter}
                                   handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderTagFilter(filter, index, selected) {
        return (
            <TagFilter key={index} ref={selected ? 'selectedFilter' : ''}
                       filter={filter}
                       selected={selected}
                       handleClickRemoveFilter={this.handleClickRemoveFilter}
                       handleChangeFilter={this.handleChangeFilterAndUnSelect}
                       handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderTagsAndChoiceFilter(filter, index, selected) {
        return (
            <TagsAndChoiceFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                 filter={filter}
                                 selected={selected}
                                 handleClickRemoveFilter={this.handleClickRemoveFilter}
                                 handleChangeFilter={this.handleChangeFilter}
                                 handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderTagsAndMultipleChoicesFilter(filter, index, selected) {
        return (
            <TagsAndMultipleChoicesFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                          filter={filter}
                                          selected={selected}
                                          handleClickRemoveFilter={this.handleClickRemoveFilter}
                                          handleChangeFilter={this.handleChangeFilter}
                                          handleClickFilter={this.handleClickFilter}
            />
        );
    }

    handleChangeFilter(filter) {
        let {filters} = this.state;
        filters[this.findSelectedFilterIndex()] = filter;
        this.setState({
            filters: filters,
            selectedFilter: filter
        });
    }

    handleChangeFilterAndUnSelect(filter) {
        let {filters} = this.state;
        filters[this.findSelectedFilterIndex()] = filter;
        this.setState({
            filters: filters,
            selectedFilter: {}
        });
    }

    handleClickFilter(key) {
        let filters = this.state.filters;
        this.setState({
            selectedFilter: filters.find(filter => filter.key === key)
        });
    }

    handleClickRemoveFilter() {
        let filters = this.state.filters;
        filters.splice(this.findSelectedFilterIndex(), 1);
        this.setState({
            filters: filters,
            selectedFilter: {}
        })
    }

    handleClickOutside(e) {
        const selectedFilter = this.refs.selectedFilter;
        if (selectedFilter && selectedFilter.getSelectedFilter() && !selectedFilter.selectedFilterContains(e.target)) {
            this.setState({selectedFilter: {}});
        }
    }

    findSelectedFilterIndex() {
        let {filters, selectedFilter} = this.state;
        let index = filters.findIndex(savedFilter => savedFilter.key === selectedFilter.key);

        return index > -1 ? index : null;
    }

    scrollToFilter() {
        clearTimeout(this.selectFilterTimeout);
        this.selectFilterTimeout = setTimeout(() => {
            let selectedFilterElem = this.refs.selectedFilter;
            if (selectedFilterElem) {
                selectedFilterElem.getSelectedFilter().scrollIntoView();
                document.getElementsByClassName('view')[0].scrollTop -= 100;
            }
        }, 0);
    }

    createThread() {
        let data = {
            name: this.props.threadName,
            filters: {profileFilters: {}, userFilters: {}},
            category: 'ThreadUsers'
        };

        let stateFilters = this.state.filters;

        for (let stateFilter of stateFilters) {
            //TODO: Improve reading from metadata
            let box = 'profileFilters';
            switch (stateFilter.type) {
                case 'choice':
                    data.filters[box][stateFilter.key] = stateFilter.choice;
                    break;
                case 'location_distance':
                    data.filters[box][stateFilter.key] = {};
                    data.filters[box][stateFilter.key]['location'] = stateFilter.value;
                    data.filters[box][stateFilter.key]['distance'] = 50;
                    break;
                case 'tags':
                    data.filters[box][stateFilter.key] = stateFilter.values;
                    break;
                case 'tags_and_choice':
                    data.filters[box][stateFilter.key] = {};
                    stateFilter.values.forEach(function(value, key){
                        data.filters[box][stateFilter.key][key] = {'tag':value.tag, 'choice':value.choice};
                    });
                    break;
                case 'tags_and_multiple_choices':
                    data.filters[box][stateFilter.key] = {};
                    stateFilter.values.forEach(function(value, key){
                        data.filters[box][stateFilter.key][key] = {'tag':value.tag, 'choices':value.choices};
                    });
                    break;
                case 'double_choice':
                    data.filters[box][stateFilter.key] = {};
                    data.filters[box][stateFilter.key]['choice'] = stateFilter.choice;
                    data.filters[box][stateFilter.key]['detail'] = stateFilter.detail;
                    break;
                case 'multiple_choices':
                    data.filters[box][stateFilter.key] = stateFilter.values;
                    break;
                case 'double_multiple_choices':
                    data.filters[box][stateFilter.key] = stateFilter.values;
                    break;
                case 'integer_range':
                    data.filters[box][stateFilter.key] = {};
                    data.filters[box][stateFilter.key]['min'] = stateFilter.value_min;
                    data.filters[box][stateFilter.key]['max'] = stateFilter.value_max;
                    break;
                case 'integer':
                    if (stateFilter.key == 'similarity' || stateFilter.key == 'compatibility'){
                        box = 'userFilters';
                    }
                    data.filters[box][stateFilter.key] = stateFilter.value;
                    break;
                default:
                    break;
            }
        }
        let history = this.context.history;
        UserActionCreators.createThread(this.props.userId, data)
            .then(function(){
                history.pushState(null, `threads`);
            });
    }

    render() {
        let filtersMetadata = Object.assign({}, this.props.filters);
        Object.keys(filtersMetadata).forEach(key => filtersMetadata[key].key = key);
        return (
            this.state.selectFilter ?

Selecciona un filtro

                    <ThreadFilterList filters={this.state.filters}
                                      filtersMetadata={filtersMetadata}
                                      handleClickFilterOnList={this.handleClickFilterOnList}
                    />

                :

                    {this.renderActiveFilters()}

                                Añadir filtro

                    <FullWidthButton onClick={this.createThread}>Crear hilo</FullWidthButton>

        );
    }
}
<commit_msg>QS-987: Send every user filter as userFilters<commit_after>import React, { PropTypes, Component } from 'react';
import * as UserActionCreators from '../../actions/UserActionCreators';
import FullWidthButton from '../ui/FullWidthButton';
import ThreadFilterList from './filters/ThreadFilterList';
import LocationFilter from './filters/LocationFilter';
import IntegerRangeFilter from './filters/IntegerRangeFilter';
import IntegerFilter from './filters/IntegerFilter';
import ChoiceFilter from './filters/ChoiceFilter';
import DoubleChoiceFilter from './filters/DoubleChoiceFilter';
import MultipleChoicesFilter from './filters/MultipleChoicesFilter';
import DoubleMultipleChoicesFilter from './filters/DoubleMultipleChoicesFilter';
import TagFilter from './filters/TagFilter';
import TagsAndChoiceFilter from './filters/TagsAndChoiceFilter';
import TagsAndMultipleChoicesFilter from './filters/TagsAndMultipleChoicesFilter';
import selectn from 'selectn';

export default class CreateUsersThread extends Component {
    static contextTypes = {
        history: PropTypes.object.isRequired
    };

    static propTypes = {
        userId: PropTypes.number.isRequired,
        filters: PropTypes.object.isRequired,
        threadName: PropTypes.string
        // TODO: defFilters should be a prop
    };

    constructor(props) {
        super(props);

        this.handleClickAddFilter = this.handleClickAddFilter.bind(this);
        this.handleClickFilterOnList = this.handleClickFilterOnList.bind(this);
        this.renderActiveFilters = this.renderActiveFilters.bind(this);
        this.handleClickFilter = this.handleClickFilter.bind(this);
        this.handleClickRemoveFilter = this.handleClickRemoveFilter.bind(this);
        this.renderLocationFilter = this.renderLocationFilter.bind(this);
        this.renderChoiceFilter = this.renderChoiceFilter.bind(this);
        this.renderDoubleChoiceFilter = this.renderDoubleChoiceFilter.bind(this);
        this.renderMultipleChoicesFilter = this.renderMultipleChoicesFilter.bind(this);
        this.renderDoubleMultipleChoicesFilter = this.renderDoubleMultipleChoicesFilter.bind(this);
        this.renderIntegerRangeFilter = this.renderIntegerRangeFilter.bind(this);
        this.renderIntegerFilter = this.renderIntegerFilter.bind(this);
        this.renderTagFilter = this.renderTagFilter.bind(this);
        this.renderTagsAndChoiceFilter = this.renderTagsAndChoiceFilter.bind(this);
        this.handleChangeFilter = this.handleChangeFilter.bind(this);
        this.handleChangeFilterAndUnSelect = this.handleChangeFilterAndUnSelect.bind(this);
        this.handleClickOutside = this.handleClickOutside.bind(this);
        this.findSelectedFilterIndex = this.findSelectedFilterIndex.bind(this);
        this.scrollToFilter = this.scrollToFilter.bind(this);
        this.createThread = this.createThread.bind(this);

        this.state = {
            selectFilter: false,
            selectedFilter: {},
            filters: []
        }
    }

    componentDidMount () {
        window.nekunoContainer.addEventListener('click', this.handleClickOutside)
    }

    componentWillUnmount () {
        window.nekunoContainer.removeEventListener('click', this.handleClickOutside)
    }

    handleClickAddFilter() {
        this.setState({
            selectFilter: true
        })
    }

    handleClickFilterOnList(checked, value) {
        let filters = this.state.filters;
        let filter = filters.find(key => key === value);
        if (typeof filter == 'undefined') {
            let defaultFilters = Object.assign({}, this.props.filters);
            filter = Object.keys(defaultFilters).map(key => defaultFilters[key]).find(function (defaultFilter) {
                return defaultFilter.key === value;
            });
        }
        if (checked) {
            filters.push(filter);
            this.setState({
                selectFilter: false,
                selectedFilter: filter,
                filters: filters
            });
            this.scrollToFilter();
        } else {
            const index = filters.findIndex(savedFilter => savedFilter.key === filter.key);
            filters.splice(index, 1);
            this.setState({
                filters: filters,
                selectedFilter: {}
            });
        }
    }

    renderActiveFilters() {
        let filters = this.state.filters;
        return (
            filters.map((filter, index) => {
                const selected = this.state.selectedFilter && this.state.selectedFilter.key === filter.key;
                switch (filter.type) {
                    case 'location_distance':
                        return this.renderLocationFilter(filter, index, selected);
                        break;
                    case 'integer_range':
                        return this.renderIntegerRangeFilter(filter, index, selected);
                        break;
                    case 'integer':
                        return this.renderIntegerFilter(filter, index, selected);
                        break;
                    case 'choice':
                        return this.renderChoiceFilter(filter, index, selected);
                        break;
                    case 'double_choice':
                        return this.renderDoubleChoiceFilter(filter, index, selected);
                        break;
                    case 'multiple_choices':
                        return this.renderMultipleChoicesFilter(filter, index, selected);
                        break;
                    case 'double_multiple_choices':
                        return this.renderDoubleMultipleChoicesFilter(filter, index, selected);
                        break;
                    case 'tags_and_choice':
                        return this.renderTagsAndChoiceFilter(filter, index, selected);
                        break;
                    case 'tags_and_multiple_choices':
                        return this.renderTagsAndMultipleChoicesFilter(filter, index, selected);
                        break;
                    case 'tags':
                        return this.renderTagFilter(filter, index, selected);
                }
            })
        );
    }

    renderLocationFilter(filter, index, selected) {
        return (
            <LocationFilter key={index} ref={selected ? 'selectedFilter' : ''}
                            filter={filter}
                            selected={selected}
                            handleClickRemoveFilter={this.handleClickRemoveFilter}
                            handleChangeFilter={this.handleChangeFilterAndUnSelect}
                            handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderIntegerRangeFilter(filter, index, selected) {
        return(
            <IntegerRangeFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                filter={filter}
                                selected={selected}
                                handleClickRemoveFilter={this.handleClickRemoveFilter}
                                handleChangeFilter={this.handleChangeFilter}
                                handleClickFilter={this.handleClickFilter}
            />
        )
    }

    renderIntegerFilter(filter, index, selected) {
        return(
            <IntegerFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                filter={filter}
                                selected={selected}
                                handleClickRemoveFilter={this.handleClickRemoveFilter}
                                handleChangeFilter={this.handleChangeFilter}
                                handleClickFilter={this.handleClickFilter}
            />
        )
    }

    renderChoiceFilter(filter, index, selected) {
        return (
            <ChoiceFilter key={index} ref={selected ? 'selectedFilter' : ''}
                          filter={filter}
                          selected={selected}
                          handleClickRemoveFilter={this.handleClickRemoveFilter}
                          handleChangeFilter={this.handleChangeFilterAndUnSelect}
                          handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderDoubleChoiceFilter(filter, index, selected) {
        return (
            <DoubleChoiceFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                filter={filter}
                                selected={selected}
                                handleClickRemoveFilter={this.handleClickRemoveFilter}
                                handleChangeFilter={this.handleChangeFilter}
                                handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderMultipleChoicesFilter(filter, index, selected) {
        return (
            <MultipleChoicesFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                   filter={filter}
                                   selected={selected}
                                   handleClickRemoveFilter={this.handleClickRemoveFilter}
                                   handleChangeFilter={this.handleChangeFilter}
                                   handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderDoubleMultipleChoicesFilter(filter, index, selected) {
        return (
            <DoubleMultipleChoicesFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                   filter={filter}
                                   selected={selected}
                                   handleClickRemoveFilter={this.handleClickRemoveFilter}
                                   handleChangeFilter={this.handleChangeFilter}
                                   handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderTagFilter(filter, index, selected) {
        return (
            <TagFilter key={index} ref={selected ? 'selectedFilter' : ''}
                       filter={filter}
                       selected={selected}
                       handleClickRemoveFilter={this.handleClickRemoveFilter}
                       handleChangeFilter={this.handleChangeFilterAndUnSelect}
                       handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderTagsAndChoiceFilter(filter, index, selected) {
        return (
            <TagsAndChoiceFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                 filter={filter}
                                 selected={selected}
                                 handleClickRemoveFilter={this.handleClickRemoveFilter}
                                 handleChangeFilter={this.handleChangeFilter}
                                 handleClickFilter={this.handleClickFilter}
            />
        );
    }

    renderTagsAndMultipleChoicesFilter(filter, index, selected) {
        return (
            <TagsAndMultipleChoicesFilter key={index} ref={selected ? 'selectedFilter' : ''}
                                          filter={filter}
                                          selected={selected}
                                          handleClickRemoveFilter={this.handleClickRemoveFilter}
                                          handleChangeFilter={this.handleChangeFilter}
                                          handleClickFilter={this.handleClickFilter}
            />
        );
    }

    handleChangeFilter(filter) {
        let {filters} = this.state;
        filters[this.findSelectedFilterIndex()] = filter;
        this.setState({
            filters: filters,
            selectedFilter: filter
        });
    }

    handleChangeFilterAndUnSelect(filter) {
        let {filters} = this.state;
        filters[this.findSelectedFilterIndex()] = filter;
        this.setState({
            filters: filters,
            selectedFilter: {}
        });
    }

    handleClickFilter(key) {
        let filters = this.state.filters;
        this.setState({
            selectedFilter: filters.find(filter => filter.key === key)
        });
    }

    handleClickRemoveFilter() {
        let filters = this.state.filters;
        filters.splice(this.findSelectedFilterIndex(), 1);
        this.setState({
            filters: filters,
            selectedFilter: {}
        })
    }

    handleClickOutside(e) {
        const selectedFilter = this.refs.selectedFilter;
        if (selectedFilter && selectedFilter.getSelectedFilter() && !selectedFilter.selectedFilterContains(e.target)) {
            this.setState({selectedFilter: {}});
        }
    }

    findSelectedFilterIndex() {
        let {filters, selectedFilter} = this.state;
        let index = filters.findIndex(savedFilter => savedFilter.key === selectedFilter.key);

        return index > -1 ? index : null;
    }

    scrollToFilter() {
        clearTimeout(this.selectFilterTimeout);
        this.selectFilterTimeout = setTimeout(() => {
            let selectedFilterElem = this.refs.selectedFilter;
            if (selectedFilterElem) {
                selectedFilterElem.getSelectedFilter().scrollIntoView();
                document.getElementsByClassName('view')[0].scrollTop -= 100;
            }
        }, 0);
    }

    createThread() {
        let data = {
            name: this.props.threadName,
            filters: {profileFilters: {}, userFilters: {}},
            category: 'ThreadUsers'
        };

        let stateFilters = this.state.filters;

        for (let stateFilter of stateFilters) {
            let box = 'userFilters';
            switch (stateFilter.type) {
                case 'choice':
                    data.filters[box][stateFilter.key] = stateFilter.choice;
                    break;
                case 'location_distance':
                    data.filters[box][stateFilter.key] = {};
                    data.filters[box][stateFilter.key]['location'] = stateFilter.value;
                    data.filters[box][stateFilter.key]['distance'] = 50;
                    break;
                case 'tags':
                    data.filters[box][stateFilter.key] = stateFilter.values;
                    break;
                case 'tags_and_choice':
                    data.filters[box][stateFilter.key] = {};
                    stateFilter.values.forEach(function(value, key){
                        data.filters[box][stateFilter.key][key] = {'tag':value.tag, 'choice':value.choice};
                    });
                    break;
                case 'tags_and_multiple_choices':
                    data.filters[box][stateFilter.key] = {};
                    stateFilter.values.forEach(function(value, key){
                        data.filters[box][stateFilter.key][key] = {'tag':value.tag, 'choices':value.choices};
                    });
                    break;
                case 'double_choice':
                    data.filters[box][stateFilter.key] = {};
                    data.filters[box][stateFilter.key]['choice'] = stateFilter.choice;
                    data.filters[box][stateFilter.key]['detail'] = stateFilter.detail;
                    break;
                case 'multiple_choices':
                    data.filters[box][stateFilter.key] = stateFilter.values;
                    break;
                case 'double_multiple_choices':
                    data.filters[box][stateFilter.key] = stateFilter.values;
                    break;
                case 'integer_range':
                    data.filters[box][stateFilter.key] = {};
                    data.filters[box][stateFilter.key]['min'] = stateFilter.value_min;
                    data.filters[box][stateFilter.key]['max'] = stateFilter.value_max;
                    break;
                case 'integer':
                    data.filters[box][stateFilter.key] = stateFilter.value;
                    break;
                default:
                    break;
            }
        }
        let history = this.context.history;
        UserActionCreators.createThread(this.props.userId, data)
            .then(function(){
                history.pushState(null, `threads`);
            });
    }

    render() {
        let filtersMetadata = Object.assign({}, this.props.filters);
        Object.keys(filtersMetadata).forEach(key => filtersMetadata[key].key = key);
        return (
            this.state.selectFilter ?

Selecciona un filtro

                    <ThreadFilterList filters={this.state.filters}
                                      filtersMetadata={filtersMetadata}
                                      handleClickFilterOnList={this.handleClickFilterOnList}
                    />

                :

                    {this.renderActiveFilters()}

                                Añadir filtro

                    <FullWidthButton onClick={this.createThread}>Crear hilo</FullWidthButton>

        );
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// extension_apitest.js
// mini-framework for ExtensionApiTest browser tests

var chrome = chrome || {};
(function() {
  chrome.test = chrome.test || {};

  chrome.test.tests = chrome.test.tests || [];

  var completed = false;
  var currentTest = null;
  var lastTest = null;

  function complete() {
    completed = true;

    // Try to get the script to stop running immediately.
    // This isn't an error, just an attempt at saying "done".
    throw "completed";
  }

  // Helper function to get around the fact that function names in javascript
  // are read-only, and you can't assign one to anonymous functions.
  function testName(test) {
    return test ? (test.name || test.generatedName) : "(no test)";
  }

  chrome.test.fail = function(message) {
    if (completed) throw "completed";
    chrome.test.log("(  FAILED  ) " + testName(currentTest));

    var stack = "(no stack available)";
    try {
      crash.me += 0;  // An intentional exception to get the stack trace.
    } catch (e) {
      if (typeof(e.stack) != undefined) {
        stack = e.stack.split("\n");
        stack = stack.slice(2);  // Remove title and fail() lines.
        stack = stack.join("\n");
      }
    }

    if (!message) {
      message = "FAIL (no message)";
    }
    message += "\n" + stack;
    console.log("[FAIL] " + testName(currentTest) + ": " + message);
    chrome.test.notifyFail(message);
    complete();
  };

  function allTestsSucceeded() {
    console.log("All tests succeeded");
    if (completed) throw "completed";

    chrome.test.notifyPass();
    complete();
  }

  var pendingCallbacks = 0;

  chrome.test.callbackAdded = function() {
    pendingCallbacks++;

    return function() {
      pendingCallbacks--;
      if (pendingCallbacks == 0) {
        chrome.test.succeed();
      }
    }
  };

  chrome.test.runNextTest = function() {
    chrome.test.assertEq(0, pendingCallbacks);
    lastTest = currentTest;
    currentTest = chrome.test.tests.shift();
    if (!currentTest) {
      allTestsSucceeded();
      return;
    }
    try {
      chrome.test.log("( RUN      ) " + testName(currentTest));
      currentTest.call();
    } catch (e) {
      var message = e.stack || "(no stack available)";
      console.log("[FAIL] " + testName(currentTest) + ": " + message);
      chrome.test.notifyFail(message);
      complete();
    }
  };

  chrome.test.succeed = function() {
    console.log("[SUCCESS] " + testName(currentTest));
    chrome.test.log("(  SUCCESS )");
    // Use setTimeout here to allow previous test contexts to be
    // eligible for garbage collection.
    setTimeout(chrome.test.runNextTest, 0);
  };

  chrome.test.assertTrue = function(test, message) {
    chrome.test.assertBool(test, true, message);
  };

  chrome.test.assertFalse = function(test, message) {
    chrome.test.assertBool(test, false, message);
  };

  chrome.test.assertBool = function(test, expected, message) {
    if (test !== expected) {
      if (typeof(test) == "string") {
        if (message) {
          message = test + "\n" + message;
        } else {
          message = test;
        }
      }
      chrome.test.fail(message);
    }
  };

  chrome.test.checkDeepEq = function(expected, actual) {
    var expected_defined = typeof(expected) != 'undefined';
    var actual_defined = typeof(actual) != 'undefined';
    if (!expected_defined && !actual_defined)
      return true;
    if (expected_defined != actual_defined)
      return false;
    for (var p in expected) {
      var eq = true;
      switch (typeof(expected[p])) {
        case 'object':
          eq = chrome.test.checkDeepEq(expected[p], actual[p]);
          break;
        case 'function':
          eq = (typeof(actual[p]) != 'undefined' &&
                expected[p].toString() == actual[p].toString());
          break;
        default:
          eq = (expected[p] == actual[p] &&
                typeof(expected[p]) == typeof(actual[p]));
          break;
      }
      if (!eq)
        return false;
    }
    for (var p in actual) {
      if (typeof(expected[p]) == 'undefined')
        return false;
    }
    return true;
  };

  chrome.test.assertEq = function(expected, actual, message) {
    var error_msg = "API Test Error in " + testName(currentTest);
    if (message)
      error_msg += ": " + message;
    if (typeof(expected) == 'object') {
      if (!chrome.test.checkDeepEq(expected, actual)) {
        chrome.test.fail(error_msg +
                         "\nActual: " + JSON.stringify(actual) +
                         "\nExpected: " + JSON.stringify(expected));
      }
      return;
    }
    if (expected != actual) {
      chrome.test.fail(error_msg +
                       "\nActual: " + actual + "\nExpected: " + expected);
    }
    if (typeof(expected) != typeof(actual)) {
      chrome.test.fail(error_msg +
                       " (type mismatch)\nActual Type: " + typeof(actual) +
                       "\nExpected Type:" + typeof(expected));
    }
  };

  chrome.test.assertNoLastError = function() {
    if (chrome.extension.lastError != undefined) {
      chrome.test.fail("lastError.message == " +
                       chrome.extension.lastError.message);
    }
  };

  function safeFunctionApply(func, arguments) {
    try {
      if (func) {
        func.apply(null, arguments);
      }
    } catch (e) {
      var stack = "(no stack available)";
      if (typeof(e.stack) != "undefined") {
        stack = e.stack.toString();
      }
      var msg = "Exception during execution of callback in " +
                testName(currentTest);
      msg += "\n" + stack;
      console.log("[FAIL] " + testName(currentTest) + ": " + msg);
      chrome.test.notifyFail(msg);
      complete();
    }
  };

  // Wrapper for generating test functions, that takes care of calling
  // assertNoLastError() and (optionally) succeed() for you.
  chrome.test.callback = function(func, expectedError) {
    if (func) {
      chrome.test.assertEq(typeof(func), 'function');
    }
    var callbackCompleted = chrome.test.callbackAdded();

    return function() {
      if (expectedError == null) {
        chrome.test.assertNoLastError();
      } else {
        chrome.test.assertEq(typeof(expectedError), 'string');
        chrome.test.assertTrue(chrome.extension.lastError != undefined,
            "No lastError, but expected " + expectedError);
        chrome.test.assertEq(expectedError, chrome.extension.lastError.message);
      }

      if (func) {
        safeFunctionApply(func, arguments);
      }

      callbackCompleted();
    };
  };

  chrome.test.listenOnce = function(event, func) {
    var callbackCompleted = chrome.test.callbackAdded();
    var listener = function() {
      event.removeListener(listener);
      safeFunctionApply(func, arguments);
      callbackCompleted();
    };
    event.addListener(listener);
  };

  chrome.test.listenForever = function(event, func) {
    var callbackCompleted = chrome.test.callbackAdded();

    var listener = function() {
      safeFunctionApply(func, arguments);
    };

    var done = function() {
      event.removeListener(listener);
      callbackCompleted();
    };

    event.addListener(listener);
    return done;
  };

  chrome.test.callbackPass = function(func) {
    return chrome.test.callback(func);
  };

  chrome.test.callbackFail = function(expectedError, func) {
    return chrome.test.callback(func, expectedError);
  };

  chrome.test.runTests = function(tests) {
    chrome.test.tests = tests;
    chrome.test.runNextTest();
  };
})();
<commit_msg>Adjusting `chrome.test.checkDeepEq` to support explicitly undefined properties<commit_after>// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// extension_apitest.js
// mini-framework for ExtensionApiTest browser tests

var chrome = chrome || {};
(function() {
  chrome.test = chrome.test || {};

  chrome.test.tests = chrome.test.tests || [];

  var completed = false;
  var currentTest = null;
  var lastTest = null;

  function complete() {
    completed = true;

    // Try to get the script to stop running immediately.
    // This isn't an error, just an attempt at saying "done".
    throw "completed";
  }

  // Helper function to get around the fact that function names in javascript
  // are read-only, and you can't assign one to anonymous functions.
  function testName(test) {
    return test ? (test.name || test.generatedName) : "(no test)";
  }

  chrome.test.fail = function(message) {
    if (completed) throw "completed";
    chrome.test.log("(  FAILED  ) " + testName(currentTest));

    var stack = "(no stack available)";
    try {
      crash.me += 0;  // An intentional exception to get the stack trace.
    } catch (e) {
      if (typeof(e.stack) != undefined) {
        stack = e.stack.split("\n");
        stack = stack.slice(2);  // Remove title and fail() lines.
        stack = stack.join("\n");
      }
    }

    if (!message) {
      message = "FAIL (no message)";
    }
    message += "\n" + stack;
    console.log("[FAIL] " + testName(currentTest) + ": " + message);
    chrome.test.notifyFail(message);
    complete();
  };

  function allTestsSucceeded() {
    console.log("All tests succeeded");
    if (completed) throw "completed";

    chrome.test.notifyPass();
    complete();
  }

  var pendingCallbacks = 0;

  chrome.test.callbackAdded = function() {
    pendingCallbacks++;

    return function() {
      pendingCallbacks--;
      if (pendingCallbacks == 0) {
        chrome.test.succeed();
      }
    }
  };

  chrome.test.runNextTest = function() {
    chrome.test.assertEq(0, pendingCallbacks);
    lastTest = currentTest;
    currentTest = chrome.test.tests.shift();
    if (!currentTest) {
      allTestsSucceeded();
      return;
    }
    try {
      chrome.test.log("( RUN      ) " + testName(currentTest));
      currentTest.call();
    } catch (e) {
      var message = e.stack || "(no stack available)";
      console.log("[FAIL] " + testName(currentTest) + ": " + message);
      chrome.test.notifyFail(message);
      complete();
    }
  };

  chrome.test.succeed = function() {
    console.log("[SUCCESS] " + testName(currentTest));
    chrome.test.log("(  SUCCESS )");
    // Use setTimeout here to allow previous test contexts to be
    // eligible for garbage collection.
    setTimeout(chrome.test.runNextTest, 0);
  };

  chrome.test.assertTrue = function(test, message) {
    chrome.test.assertBool(test, true, message);
  };

  chrome.test.assertFalse = function(test, message) {
    chrome.test.assertBool(test, false, message);
  };

  chrome.test.assertBool = function(test, expected, message) {
    if (test !== expected) {
      if (typeof(test) == "string") {
        if (message) {
          message = test + "\n" + message;
        } else {
          message = test;
        }
      }
      chrome.test.fail(message);
    }
  };

  chrome.test.checkDeepEq = function (expected, actual) {
    if (expected === actual)
      return true;

    if (typeof(expected) !== typeof(actual))
      return false;

    for (var p in actual) {
      if (actual.hasOwnProperty(p) && !expected.hasOwnProperty(p))
        return false;
    }
    for (var p in expected) {
      if (expected.hasOwnProperty(p) && !actual.hasOwnProperty(p))
        return false;
    }

    for (var p in expected) {
      var eq = true;
      switch (typeof(expected[p])) {
        case 'object':
          eq = chrome.test.checkDeepEq(expected[p], actual[p]);
          break;
        case 'function':
          eq = (typeof(actual[p]) != 'undefined' &&
                expected[p].toString() == actual[p].toString());
          break;
        default:
          eq = (expected[p] == actual[p] &&
                typeof(expected[p]) == typeof(actual[p]));
          break;
      }
      if (!eq)
        return false;
    }
    return true;
  };

  chrome.test.assertEq = function(expected, actual, message) {
    var error_msg = "API Test Error in " + testName(currentTest);
    if (message)
      error_msg += ": " + message;
    if (typeof(expected) == 'object') {
      if (!chrome.test.checkDeepEq(expected, actual)) {
        chrome.test.fail(error_msg +
                         "\nActual: " + JSON.stringify(actual) +
                         "\nExpected: " + JSON.stringify(expected));
      }
      return;
    }
    if (expected != actual) {
      chrome.test.fail(error_msg +
                       "\nActual: " + actual + "\nExpected: " + expected);
    }
    if (typeof(expected) != typeof(actual)) {
      chrome.test.fail(error_msg +
                       " (type mismatch)\nActual Type: " + typeof(actual) +
                       "\nExpected Type:" + typeof(expected));
    }
  };

  chrome.test.assertNoLastError = function() {
    if (chrome.extension.lastError != undefined) {
      chrome.test.fail("lastError.message == " +
                       chrome.extension.lastError.message);
    }
  };

  function safeFunctionApply(func, arguments) {
    try {
      if (func) {
        func.apply(null, arguments);
      }
    } catch (e) {
      var stack = "(no stack available)";
      if (typeof(e.stack) != "undefined") {
        stack = e.stack.toString();
      }
      var msg = "Exception during execution of callback in " +
                testName(currentTest);
      msg += "\n" + stack;
      console.log("[FAIL] " + testName(currentTest) + ": " + msg);
      chrome.test.notifyFail(msg);
      complete();
    }
  };

  // Wrapper for generating test functions, that takes care of calling
  // assertNoLastError() and (optionally) succeed() for you.
  chrome.test.callback = function(func, expectedError) {
    if (func) {
      chrome.test.assertEq(typeof(func), 'function');
    }
    var callbackCompleted = chrome.test.callbackAdded();

    return function() {
      if (expectedError == null) {
        chrome.test.assertNoLastError();
      } else {
        chrome.test.assertEq(typeof(expectedError), 'string');
        chrome.test.assertTrue(chrome.extension.lastError != undefined,
            "No lastError, but expected " + expectedError);
        chrome.test.assertEq(expectedError, chrome.extension.lastError.message);
      }

      if (func) {
        safeFunctionApply(func, arguments);
      }

      callbackCompleted();
    };
  };

  chrome.test.listenOnce = function(event, func) {
    var callbackCompleted = chrome.test.callbackAdded();
    var listener = function() {
      event.removeListener(listener);
      safeFunctionApply(func, arguments);
      callbackCompleted();
    };
    event.addListener(listener);
  };

  chrome.test.listenForever = function(event, func) {
    var callbackCompleted = chrome.test.callbackAdded();

    var listener = function() {
      safeFunctionApply(func, arguments);
    };

    var done = function() {
      event.removeListener(listener);
      callbackCompleted();
    };

    event.addListener(listener);
    return done;
  };

  chrome.test.callbackPass = function(func) {
    return chrome.test.callback(func);
  };

  chrome.test.callbackFail = function(expectedError, func) {
    return chrome.test.callback(func, expectedError);
  };

  chrome.test.runTests = function(tests) {
    chrome.test.tests = tests;
    chrome.test.runNextTest();
  };
})();
<|endoftext|>


<commit_before>
import path from 'path';
import os from 'os';
import should from 'should';
import ElectronManager from '../src/ElectronManager';

/* eslint padded-blocks: [0] */
describe('electron workers', () => {

  it('should pass worker id as an env var', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'workerId.js'),
      numberOfWorkers: 2
    });

    electronManager.start((startErr) => {
      let workersResponseId = [],
          isDone = false,
          executeCount = 0;

      if (startErr) {
        return done(startErr);
      }

      function executeTask() {
        electronManager.execute({}, (executeErr, workerIdResp) => {
          if (isDone) {
            return;
          }

          if (executeErr) {
            isDone = true;
            done(executeErr);
            return;
          }

          workersResponseId.push(workerIdResp);

          executeCount++;

          if (executeCount === electronManager._electronInstances.length) {
            let workerIds,
                workersNotFound;

            workerIds = electronManager._electronInstances.map((worker) => {
              return worker.id;
            });

            workersNotFound = workerIds.filter((workerId) => {
              return workersResponseId.indexOf(workerId) === -1;
            });

            should(workersNotFound.length).be.eql(0);
            electronManager.kill();
            done();
          }
        });
      }

      for (let ix = 0; ix < electronManager._electronInstances.length; ix++) {
        executeTask();
      }
    });
  });

  it('should pass env vars', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'env.js'),
      numberOfWorkers: 1,
      env: {
        FOO: 'FOO',
        CUSTOM_ENV: 'FOO'
      }
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ FOO: 'FOO', CUSTOM_ENV: 'FOO' });
        electronManager.kill();
        done();
      });
    });
  });

  it('should pass custom kill signal for worker process', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      killSignal: 'SIGKILL'
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances[0].options.killSignal).be.eql('SIGKILL');
      electronManager.kill();
      done();
    });
  });

  it('should pass custom stdio to worker child process', function(done) {
    let customStdio = [null, null, null, 'ipc'];

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'ipc.js'),
      numberOfWorkers: 1,
      stdio: customStdio
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager._electronInstances[0].options.stdio.forEach(function(value, index) {
        should(value).be.eql(customStdio[index]);
      });

      electronManager._electronInstances[0]._childProcess.on('message', function(msg) {
        should(msg).be.eql('ping');
        electronManager.kill();
        done();
      });

      electronManager.execute({}, (executeErr) => {
        if (executeErr) {
          return done(executeErr);
        }
      });
    });
  });

  it('should pass custom arguments to the electron executable', function(done) {
    let electronManager = new ElectronManager({
      electronArgs: ['--some-value=2', '--enable-some-behaviour'],
      pathToScript: path.join(__dirname, 'electron-script', 'custom-args.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data.join(' ')).be.eql('--some-value=2 --enable-some-behaviour');
        electronManager.kill();
        done();
      });
    });
  });

  it('should spin up number of workers equal to number of cores by default', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js')
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances.length).be.eql(os.cpus().length);
      electronManager.kill();
      done();
    });
  });

  it('should spin up specified number of workers', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 3
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances.length).be.eql(3);
      electronManager.kill();
      done();
    });
  });

  it('should initialize free workers', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 2
    });

    electronManager.start((startErr) => {
      let busyWorkers;

      if (startErr) {
        return done(startErr);
      }

      busyWorkers = electronManager._electronInstances.filter((worker) => {
        return worker.isBusy === true;
      });

      should(busyWorkers.length).be.eql(0);
      electronManager.kill();
      done();
    });
  });

  it('should initialize workers correctly with port boundary', function(done) {
    let isDone = false;

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 2,
      portLeftBoundary: 10000,
      portRightBoundary: 15000
    });

    electronManager.on('workerRecycling', function() {
      if (isDone) {
        return;
      }

      isDone = true;
      done(new Error('worker was recycled when trying to use port boundary'));
    });

    electronManager.start((startErr) => {
      if (isDone) {
        return;
      }

      if (startErr) {
        isDone = true;
        return done(startErr);
      }

      should(electronManager._electronInstances.length).be.eql(2);
      should(electronManager._electronInstances[0].port).not.be.eql(electronManager._electronInstances[1].port);
      electronManager.kill();
      done();
    });
  });

  it('should distribute tasks across all workers', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 4
    });

    electronManager.start((startErr) => {
      let workersCalled = [],
          isDone = false,
          executeCount = 0;

      if (startErr) {
        return done(startErr);
      }

      electronManager._electronInstances.forEach((worker) => {
        worker.once('task', function() {
          workersCalled.push(worker.id);
        });
      });

      function executeTask() {
        electronManager.execute({}, (executeErr) => {
          if (isDone) {
            return;
          }

          if (executeErr) {
            isDone = true;
            done(executeErr);
            return;
          }

          executeCount++;

          if (executeCount === electronManager._electronInstances.length) {
            let workerIds,
                workersNotCalled;

            workerIds = electronManager._electronInstances.map((worker) => {
              return worker.id;
            });

            workersNotCalled = workerIds.filter((workerId) => {
              return workersCalled.indexOf(workerId) === -1;
            });

            should(workersNotCalled.length).be.eql(0);
            electronManager.kill();
            done();
          }
        });
      }

      for (let ix = 0; ix < electronManager._electronInstances.length; ix++) {
        executeTask();
      }
    });
  });

  it('should be able to communicate with slowly starting electron', function(done) {
    this.timeout(5000);

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'slowstart.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({ foo: 'test' }, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ ok: true });
        electronManager.kill();
        done();
      });
    });
  });

  it('should be able to communicate with electron', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({ foo: 'test' }, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ foo: 'test' });
        electronManager.kill();
        done();
      });
    });
  });

  it('should be able to start electron in a port range', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1,
      portLeftBoundary: 10000,
      portRightBoundary: 11000
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances[0].port).be.within(10000, 11000);
      electronManager.kill();
      done();
    });
  });

  it('should be able to communicate with just-port script', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'just-port.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({ foo: 'test' }, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ foo: 'test' });
        electronManager.kill();
        done();
      });
    });
  });

  it('should be able to send just a simple string on input', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute('test', (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql('test');
        electronManager.kill();
        done();
      });
    });
  });

  it('simple input string should not be stringified what is causing broken line endings', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute('<style> td { \n background-color: red \n } </style>', (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql('<style> td { \n background-color: red \n } </style>');
        electronManager.kill();
        done();
      });
    });
  });

  it('worker process creation should emit event', function(done) {
    let numberOfProcess = 0;

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 2
    });

    electronManager.on('workerProcessCreated', function(worker) {
      numberOfProcess++;

      let matchWorker = electronManager._electronInstances.filter((electronInstance) => {
        return electronInstance.id === worker.id;
      });

      should(matchWorker.length).be.eql(1);

      if (numberOfProcess === 2) {
        electronManager.kill();
        done();
      }
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }
    });
  });

  it('worker process recycle should emit event', function(done) {
    let isDone = false,
        recyclingWasCalled = false;

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.on('workerRecycling', () => {
      recyclingWasCalled = true;
    });

    electronManager.on('workerRecycled', (worker) => {
      if (isDone) {
        return;
      }

      isDone = true;
      should(recyclingWasCalled).be.eql(true);
      should(worker.id).be.eql(electronManager._electronInstances[0].id);
      electronManager.kill();
      done();
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager._electronInstances[0].recycle(function(recycleErr) {
        if (isDone) {
          return;
        }

        if (recycleErr) {
          isDone = true;
          return done(recycleErr);
        }
      });
    });
  });

  it('timeout should emit event', function(done) {
    this.timeout(10000);

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'timeout.js'),
      numberOfWorkers: 1,
      timeout: 10
    });

    let emitted = false,
        timeoutId;

    electronManager.on('workerTimeout', () => {
      emitted = true;
      clearTimeout(timeoutId);
      electronManager.kill();
      done();
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr) => {
        if (!executeErr) {
          return done(new Error('should not execute successfully'));
        }
      });
    });

    timeoutId = setTimeout(() => {
      electronManager.kill();

      if (!emitted) {
        done(new Error('worker was not recycled'));
      }
    }, 10000);
  });

  it('timeout should cb with error', function(done) {
    this.timeout(10000);

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'timeout.js'),
      numberOfWorkers: 1,
      timeout: 100
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr) => {
        if (!executeErr) {
          return done(new Error('should not execute successfully'));
        }

        electronManager.kill();
        should(executeErr.workerTimeout).be.eql(true);
        done();
      });
    });
  });

});
<commit_msg>[update] error message in timeout test<commit_after>
import path from 'path';
import os from 'os';
import should from 'should';
import ElectronManager from '../src/ElectronManager';

/* eslint padded-blocks: [0] */
describe('electron workers', () => {

  it('should pass worker id as an env var', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'workerId.js'),
      numberOfWorkers: 2
    });

    electronManager.start((startErr) => {
      let workersResponseId = [],
          isDone = false,
          executeCount = 0;

      if (startErr) {
        return done(startErr);
      }

      function executeTask() {
        electronManager.execute({}, (executeErr, workerIdResp) => {
          if (isDone) {
            return;
          }

          if (executeErr) {
            isDone = true;
            done(executeErr);
            return;
          }

          workersResponseId.push(workerIdResp);

          executeCount++;

          if (executeCount === electronManager._electronInstances.length) {
            let workerIds,
                workersNotFound;

            workerIds = electronManager._electronInstances.map((worker) => {
              return worker.id;
            });

            workersNotFound = workerIds.filter((workerId) => {
              return workersResponseId.indexOf(workerId) === -1;
            });

            should(workersNotFound.length).be.eql(0);
            electronManager.kill();
            done();
          }
        });
      }

      for (let ix = 0; ix < electronManager._electronInstances.length; ix++) {
        executeTask();
      }
    });
  });

  it('should pass env vars', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'env.js'),
      numberOfWorkers: 1,
      env: {
        FOO: 'FOO',
        CUSTOM_ENV: 'FOO'
      }
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ FOO: 'FOO', CUSTOM_ENV: 'FOO' });
        electronManager.kill();
        done();
      });
    });
  });

  it('should pass custom kill signal for worker process', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      killSignal: 'SIGKILL'
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances[0].options.killSignal).be.eql('SIGKILL');
      electronManager.kill();
      done();
    });
  });

  it('should pass custom stdio to worker child process', function(done) {
    let customStdio = [null, null, null, 'ipc'];

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'ipc.js'),
      numberOfWorkers: 1,
      stdio: customStdio
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager._electronInstances[0].options.stdio.forEach(function(value, index) {
        should(value).be.eql(customStdio[index]);
      });

      electronManager._electronInstances[0]._childProcess.on('message', function(msg) {
        should(msg).be.eql('ping');
        electronManager.kill();
        done();
      });

      electronManager.execute({}, (executeErr) => {
        if (executeErr) {
          return done(executeErr);
        }
      });
    });
  });

  it('should pass custom arguments to the electron executable', function(done) {
    let electronManager = new ElectronManager({
      electronArgs: ['--some-value=2', '--enable-some-behaviour'],
      pathToScript: path.join(__dirname, 'electron-script', 'custom-args.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data.join(' ')).be.eql('--some-value=2 --enable-some-behaviour');
        electronManager.kill();
        done();
      });
    });
  });

  it('should spin up number of workers equal to number of cores by default', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js')
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances.length).be.eql(os.cpus().length);
      electronManager.kill();
      done();
    });
  });

  it('should spin up specified number of workers', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 3
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances.length).be.eql(3);
      electronManager.kill();
      done();
    });
  });

  it('should initialize free workers', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 2
    });

    electronManager.start((startErr) => {
      let busyWorkers;

      if (startErr) {
        return done(startErr);
      }

      busyWorkers = electronManager._electronInstances.filter((worker) => {
        return worker.isBusy === true;
      });

      should(busyWorkers.length).be.eql(0);
      electronManager.kill();
      done();
    });
  });

  it('should initialize workers correctly with port boundary', function(done) {
    let isDone = false;

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 2,
      portLeftBoundary: 10000,
      portRightBoundary: 15000
    });

    electronManager.on('workerRecycling', function() {
      if (isDone) {
        return;
      }

      isDone = true;
      done(new Error('worker was recycled when trying to use port boundary'));
    });

    electronManager.start((startErr) => {
      if (isDone) {
        return;
      }

      if (startErr) {
        isDone = true;
        return done(startErr);
      }

      should(electronManager._electronInstances.length).be.eql(2);
      should(electronManager._electronInstances[0].port).not.be.eql(electronManager._electronInstances[1].port);
      electronManager.kill();
      done();
    });
  });

  it('should distribute tasks across all workers', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 4
    });

    electronManager.start((startErr) => {
      let workersCalled = [],
          isDone = false,
          executeCount = 0;

      if (startErr) {
        return done(startErr);
      }

      electronManager._electronInstances.forEach((worker) => {
        worker.once('task', function() {
          workersCalled.push(worker.id);
        });
      });

      function executeTask() {
        electronManager.execute({}, (executeErr) => {
          if (isDone) {
            return;
          }

          if (executeErr) {
            isDone = true;
            done(executeErr);
            return;
          }

          executeCount++;

          if (executeCount === electronManager._electronInstances.length) {
            let workerIds,
                workersNotCalled;

            workerIds = electronManager._electronInstances.map((worker) => {
              return worker.id;
            });

            workersNotCalled = workerIds.filter((workerId) => {
              return workersCalled.indexOf(workerId) === -1;
            });

            should(workersNotCalled.length).be.eql(0);
            electronManager.kill();
            done();
          }
        });
      }

      for (let ix = 0; ix < electronManager._electronInstances.length; ix++) {
        executeTask();
      }
    });
  });

  it('should be able to communicate with slowly starting electron', function(done) {
    this.timeout(5000);

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'slowstart.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({ foo: 'test' }, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ ok: true });
        electronManager.kill();
        done();
      });
    });
  });

  it('should be able to communicate with electron', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({ foo: 'test' }, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ foo: 'test' });
        electronManager.kill();
        done();
      });
    });
  });

  it('should be able to start electron in a port range', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1,
      portLeftBoundary: 10000,
      portRightBoundary: 11000
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      should(electronManager._electronInstances[0].port).be.within(10000, 11000);
      electronManager.kill();
      done();
    });
  });

  it('should be able to communicate with just-port script', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'just-port.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({ foo: 'test' }, (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql({ foo: 'test' });
        electronManager.kill();
        done();
      });
    });
  });

  it('should be able to send just a simple string on input', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute('test', (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql('test');
        electronManager.kill();
        done();
      });
    });
  });

  it('simple input string should not be stringified what is causing broken line endings', function(done) {
    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute('<style> td { \n background-color: red \n } </style>', (executeErr, data) => {
        if (executeErr) {
          return done(executeErr);
        }

        should(data).be.eql('<style> td { \n background-color: red \n } </style>');
        electronManager.kill();
        done();
      });
    });
  });

  it('worker process creation should emit event', function(done) {
    let numberOfProcess = 0;

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 2
    });

    electronManager.on('workerProcessCreated', function(worker) {
      numberOfProcess++;

      let matchWorker = electronManager._electronInstances.filter((electronInstance) => {
        return electronInstance.id === worker.id;
      });

      should(matchWorker.length).be.eql(1);

      if (numberOfProcess === 2) {
        electronManager.kill();
        done();
      }
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }
    });
  });

  it('worker process recycle should emit event', function(done) {
    let isDone = false,
        recyclingWasCalled = false;

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'script.js'),
      numberOfWorkers: 1
    });

    electronManager.on('workerRecycling', () => {
      recyclingWasCalled = true;
    });

    electronManager.on('workerRecycled', (worker) => {
      if (isDone) {
        return;
      }

      isDone = true;
      should(recyclingWasCalled).be.eql(true);
      should(worker.id).be.eql(electronManager._electronInstances[0].id);
      electronManager.kill();
      done();
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager._electronInstances[0].recycle(function(recycleErr) {
        if (isDone) {
          return;
        }

        if (recycleErr) {
          isDone = true;
          return done(recycleErr);
        }
      });
    });
  });

  it('timeout should emit event', function(done) {
    this.timeout(10000);

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'timeout.js'),
      numberOfWorkers: 1,
      timeout: 10
    });

    let emitted = false,
        timeoutId;

    electronManager.on('workerTimeout', () => {
      emitted = true;
      clearTimeout(timeoutId);
      electronManager.kill();
      done();
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr) => {
        if (!executeErr) {
          return done(new Error('should not execute successfully'));
        }
      });
    });

    timeoutId = setTimeout(() => {
      electronManager.kill();

      if (!emitted) {
        done(new Error('worker timeout was not emitted'));
      }
    }, 10000);
  });

  it('timeout should cb with error', function(done) {
    this.timeout(10000);

    let electronManager = new ElectronManager({
      pathToScript: path.join(__dirname, 'electron-script', 'timeout.js'),
      numberOfWorkers: 1,
      timeout: 100
    });

    electronManager.start((startErr) => {
      if (startErr) {
        return done(startErr);
      }

      electronManager.execute({}, (executeErr) => {
        if (!executeErr) {
          return done(new Error('should not execute successfully'));
        }

        electronManager.kill();
        should(executeErr.workerTimeout).be.eql(true);
        done();
      });
    });
  });

});
<|endoftext|>


<commit_before>TestRunner.test({
    name: 'Text can be parsed into a Template and fragment',
    test: function (input, callback) {
        var template = new Template('\r\n\

Widget Demo
\r\n\
            @if: false {\r\n\

boolean constant false
\r\n\
            }\r\n\
            @if: true {\r\n\

boolean constant true
\r\n\
        	}\r\n\
        ');
        document.body.appendChild(template.build());
        callback(template);
    },
    assert: function (result, callback) {
        console.log(result);
        callback(true);
    }
});
<commit_msg>Fixed Cascade test0.<commit_after>TestRunner.test({
    name: 'Text can be parsed into a Template and fragment',
    test: function (input, callback) {
        var template = new Template('\r\n\

Widget Demo
\r\n\
            @if: false {\r\n\

boolean constant false
\r\n\
            }\r\n\
            @if: true {\r\n\

boolean constant true
\r\n\
        	}\r\n\
        ');
        callback(template.build());
    },
    assert: function (result, callback) {
        document.body.appendChild(result);
        var testFalse = document.getElementById("testFalse");
        var testTrue = document.getElementById("testTrue");
        callback(testTrue && !testFalse);
    }
});
<|endoftext|>


<commit_before>'use strict';

/**
 * WebSocket server for real-time client-server communication.
 */

var WebSocketServer = require('ws').Server,
    url = require('url'),
    jwt = require('koa-jwt'),
    _ = require('lodash'),
    config = require('../config/config');

/**
 * Creates and attaches a WebSocket server to a given HTTP server with the same host URI.
 * @param server Node.js HTTP server instance.
 * @returns {WebSocketServer}
 */
exports.create = function (server) {
  // create a new WebSocket server
  var wss = new WebSocketServer({server: server, verifyClient: function (info) {
    // validate the connecting client's access token
    // validator function attaches user details to the request object if token is valid
    var query = url.parse(info.req.url, true).query,
        accessToken = query.access_token;

    // this bit is a bit of hack to make a sync function out of thunkified async function! might better use 'jws' (npm) package here
    jwt.verify(accessToken, config.app.secret)(function (err, jwtPayload) {
      info.req.user = jwtPayload;
    });

    return info.req.user;
  }});

  // WebSocket event that is fired when a new client is validated and connected
  wss.on('connection', function (ws) {
    var user = ws.upgradeReq.user;
    console.log('A new WebSocket client connected with ID: ' + user.id);

    // associate connecting user ID with WebSocket connection in the clients dictionary
    if (exports.clients[user.id]) {
      exports.clients[user.id].push(ws);
    }
    else {
      exports.clients[user.id] = [ws];
    }

    ws.on('close', function () {
      console.log('A WebSocket client with ID: ' + user.id + ' disconnected.');
      if (exports.clients[user.id].length === 1) {
        exports.clients[user.id] = null; // exports.clients.splice(user.id, 1); // this is the correct way but this also shifts all elements indexes which spoils the design here..
      }
      else {
        var index = exports.clients[user.id].indexOf(ws);
        exports.clients[user.id].splice(index, 1);
      }
    });

    ws.on('error', function (err) {
      console.log('A WebSocket error occurred: %s', err);
    });

    ws.on('message', function (data) {
      console.log('A WebSocket message received: %s', JSON.parse(data));
    });
  });

  exports.wss = wss;
  return wss;
};

/**
 * Dictionary (array of arrays) to math connected user IDs with connected clients (i.e. one user might be connected from multiple devices).
 * Array index is the client ID and the stored is also an array with socket client objects. i.e. clients[33452234]: [ws1, ws2, ws3...]
 */
exports.clients = [];

/**
 *
 * @param recipients - Array of user IDs to send the data to. Only the online clients receive the data out of the entire given recipient list. If omitted, all users will receive the message.
 * @param data - JS object (which will be serialized to JSON before being sent to the clients).
 * @param callback - Optional callback to be called when
 */
exports.send = function (recipients, data, callback) {
  if (!Array.isArray(recipients)) {
    data = recipients;
    recipients = _.keys(exports.clients);
  }
  data = JSON.stringify(data);

  if (!callback) {
    // send the data to only the online recipients
    var send = function (client) {
      client.send(data, handleWsError);
    };

    for (var i = 0; i < recipients.length; i++) {
      var recipient = recipients[i],
          onlineClients = exports.clients[recipient];
      if (onlineClients) {
        onlineClients.forEach(send);
      }
    }
  } else {
    // this part requires recursive async callback series while passing on the err array to the callback function in the end..
  }
};

function handleWsError(err) {
  /* ignore errors */
  // The callback is also the only way of being notified that data has actually been sent.
  // if error is null, the send has been completed,
  // otherwise the error object will indicate what failed.
  if (err) {
    console.log('A WebSocket error occurred: %s', err);
  }
}

// shorthand functions for
exports.postCreated = function (post) {
  exports.send({jsonrpc: '2.0', method: 'posts.created', params: post});
};

exports.commentCreated = function (comment) {
  exports.send({jsonrpc: '2.0', method: 'posts.comments.created', params: comment});
};

// ######## Experimental bi-directional WebSocket support [KOA-WS BEGIN] ########

exports.middleware = function () {

  return function *wsMiddleware(next) {
    // 1. check if this response is for a json-rpc request (with a proper id) through websocket, or a normal http request
    // 2. check if we need to broadcast anything to anyone even if it's not a direct response for a ws request

    // we need to be the last in the stack to handle the request so yield down
    yield next;

    // block koa from sending the response over http only if this was a request streaming from websockets
    if (!this.websocket) {
      return;
    }

    this.respond = false;
    console.log(exports.wss._server._events.request);
  };
};

// ######## Experimental bi-directional WebSocket support [KOA-WS END] ########<commit_msg>add more exp features<commit_after>'use strict';

/**
 * WebSocket server for real-time client-server communication.
 */

var WebSocketServer = require('ws').Server,
    url = require('url'),
    jwt = require('koa-jwt'),
    _ = require('lodash'),
    config = require('../config/config');

/**
 * Creates and attaches a WebSocket server to a given HTTP server with the same host URI.
 * @param server Node.js HTTP server instance.
 * @returns {WebSocketServer}
 */
exports.create = function (server) {
  // create a new WebSocket server
  var wss = new WebSocketServer({server: server, verifyClient: function (info) {
    // validate the connecting client's access token
    // validator function attaches user details to the request object if token is valid
    var query = url.parse(info.req.url, true).query,
        accessToken = query.access_token;

    // this bit is a bit of hack to make a sync function out of thunkified async function! might better use 'jws' (npm) package here
    jwt.verify(accessToken, config.app.secret)(function (err, jwtPayload) {
      info.req.user = jwtPayload;
    });

    return info.req.user;
  }});

  // WebSocket event that is fired when a new client is validated and connected
  wss.on('connection', function (ws) {
    var user = ws.upgradeReq.user;
    console.log('A new WebSocket client connected with ID: ' + user.id);

    // associate connecting user ID with WebSocket connection in the clients dictionary
    if (exports.clients[user.id]) {
      exports.clients[user.id].push(ws);
    }
    else {
      exports.clients[user.id] = [ws];
    }

    ws.on('close', function () {
      console.log('A WebSocket client with ID: ' + user.id + ' disconnected.');
      if (exports.clients[user.id].length === 1) {
        exports.clients[user.id] = null; // exports.clients.splice(user.id, 1); // this is the correct way but this also shifts all elements indexes which spoils the design here..
      }
      else {
        var index = exports.clients[user.id].indexOf(ws);
        exports.clients[user.id].splice(index, 1);
      }
    });

    ws.on('error', function (err) {
      console.log('A WebSocket error occurred: %s', err);
    });

    ws.on('message', function (data) {
      console.log('A WebSocket message received: %s', JSON.parse(data));
    });
  });

  exports.wss = wss;
  return wss;
};

/**
 * Dictionary (array of arrays) to math connected user IDs with connected clients (i.e. one user might be connected from multiple devices).
 * Array index is the client ID and the stored is also an array with socket client objects. i.e. clients[33452234]: [ws1, ws2, ws3...]
 */
exports.clients = [];

/**
 *
 * @param recipients - Array of user IDs to send the data to. Only the online clients receive the data out of the entire given recipient list. If omitted, all users will receive the message.
 * @param data - JS object (which will be serialized to JSON before being sent to the clients).
 * @param callback - Optional callback to be called when
 */
exports.send = function (recipients, data, callback) {
  if (!Array.isArray(recipients)) {
    data = recipients;
    recipients = _.keys(exports.clients);
  }
  data = JSON.stringify(data);

  if (!callback) {
    // send the data to only the online recipients
    var send = function (client) {
      client.send(data, handleWsError);
    };

    for (var i = 0; i < recipients.length; i++) {
      var recipient = recipients[i],
          onlineClients = exports.clients[recipient];
      if (onlineClients) {
        onlineClients.forEach(send);
      }
    }
  } else {
    // this part requires recursive async callback series while passing on the err array to the callback function in the end..
  }
};

function handleWsError(err) {
  /* ignore errors */
  // The callback is also the only way of being notified that data has actually been sent.
  // if error is null, the send has been completed,
  // otherwise the error object will indicate what failed.
  if (err) {
    console.log('A WebSocket error occurred: %s', err);
  }
}

// shorthand functions for
exports.postCreated = function (post) {
  exports.send({jsonrpc: '2.0', method: 'posts.created', params: post});
};

exports.commentCreated = function (comment) {
  exports.send({jsonrpc: '2.0', method: 'posts.comments.created', params: comment});
};

// ######## Experimental bi-directional WebSocket support [KOA-WS BEGIN] ########

exports.middleware = function () {

  // exports.wss._server._events.request({websocket: true, url: '/', headers: {}, socket: {writable: false}}, {setHeader: function () {}, once: function () {}});

  return function *wsMiddleware(next) {
    // 1. check if this response is for a json-rpc request (with a proper id) through websocket, or a normal http request
    // 2. check if we need to broadcast anything to anyone even if it's not a direct response for a ws request

    // we need to be the last in the stack to handle the request so yield down
    yield next;

    // block koa from sending the response over http only if this was a request streaming from websockets
    if (!this.websocket) {
      return;
    }

    this.respond = false;
    console.log(exports.wss._server._events.request);
  };
};

// ######## Experimental bi-directional WebSocket support [KOA-WS END] ########<|endoftext|>