<commit_before>import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import shortid from 'shortid';

import Action from './Action';
import arrayFrom from './arrayFrom';
import ListBox from './ListBox';

const optionShape = PropTypes.shape({
    value: PropTypes.any.isRequired,
    label: PropTypes.string.isRequired,
});

const defaultFilter = (option, filterInput) => {
    if (filterInput === '') {
        return true;
    }

    return (new RegExp(filterInput, 'i')).test(option.label);
};

class DualListBox extends React.Component {
    static propTypes = {
        options: PropTypes.arrayOf(
            PropTypes.oneOfType([
                optionShape,
                PropTypes.shape({
                    value: PropTypes.any,
                    options: PropTypes.arrayOf(optionShape),
                }),
            ]),
        ).isRequired,
        onChange: PropTypes.func.isRequired,

        alignActions: PropTypes.string,
        available: PropTypes.arrayOf(PropTypes.string),
        availableRef: PropTypes.func,
        canFilter: PropTypes.bool,
        disabled: PropTypes.bool,
        filter: PropTypes.shape({
            available: PropTypes.string.isRequired,
            selected: PropTypes.string.isRequired,
        }),
        filterCallback: PropTypes.func,
        filterPlaceholder: PropTypes.string,
        name: PropTypes.string,
        preserveSelectOrder: PropTypes.bool,
        selected: PropTypes.arrayOf(PropTypes.string),
        selectedRef: PropTypes.func,
        onFilterChange: PropTypes.func,
    };

    static defaultProps = {
        alignActions: 'middle',
        available: undefined,
        availableRef: null,
        canFilter: false,
        disabled: false,
        filter: null,
        filterPlaceholder: 'Search...',
        filterCallback: defaultFilter,
        name: null,
        preserveSelectOrder: null,
        selected: [],
        selectedRef: null,
        onFilterChange: null,
    };

    /**
     * @param {Object} props
     *
     * @returns {void}
     */
    constructor(props) {
        super(props);

        this.state = {
            filter: props.filter ? props.filter : {
                available: '',
                selected: '',
            },
        };

        this.onClick = this.onClick.bind(this);
        this.onDoubleClick = this.onDoubleClick.bind(this);
        this.onKeyUp = this.onKeyUp.bind(this);
        this.onFilterChange = this.onFilterChange.bind(this);

        this.id = shortid.generate();
    }

    /**
     * @param {Object} filter
     *
     * @returns {void}
     */
    componentWillReceiveProps({ filter }) {
        if (filter !== null) {
            this.setState({ filter });
        }
    }

    /**
     * @param {string} direction
     * @param {boolean} isMoveAll
     *
     * @returns {void}
     */
    onClick({ direction, isMoveAll }) {
        const { options, onChange } = this.props;
        const select = direction === 'right' ? this.available : this.selected;

        let selected = [];

        if (isMoveAll) {
            selected = direction === 'right' ? this.makeOptionsSelected(options) : [];
        } else {
            selected = this.toggleSelected(
                this.getSelectedOptions(select),
            );
        }

        onChange(selected);
    }

    /**
     * @param {Object} event
     *
     * @returns {void}
     */
    onDoubleClick(event) {
        const value = event.currentTarget.value;
        const selected = this.toggleSelected([value]);

        this.props.onChange(selected);
    }

    /**
     * @param {Event} event
     *
     * @returns {void}
     */
    onKeyUp(event) {
        const { currentTarget, key } = event;

        if (key === 'Enter') {
            const selected = this.toggleSelected(
                arrayFrom(currentTarget.options)
                    .filter(option => option.selected)
                    .map(option => option.value),
            );

            this.props.onChange(selected);
        }
    }

    /**
     * @param {Event} event
     *
     * @returns {void}
     */
    onFilterChange(event) {
        const { onFilterChange } = this.props;

        const newFilter = {
            ...this.state.filter,
            [event.target.dataset.key]: event.target.value,
        };

        if (onFilterChange) {
            onFilterChange(newFilter);
        } else {
            this.setState({ filter: newFilter });
        }
    }

    /**
     * Converts a flat array to a key/value mapping.
     *
     * @param {Array} options
     *
     * @returns {Object}
     */
    getLabelMap(options) {
        let labelMap = {};

        options.forEach((option) => {
            if (option.options !== undefined) {
                labelMap = { ...labelMap, ...this.getLabelMap(option.options) };
            } else {
                labelMap[option.value] = option.label;
            }
        });

        return labelMap;
    }

    /**
     * Returns the selected options from a given element.
     *
     * @param {Object} element
     *
     * @returns {Array}
     */
    getSelectedOptions(element) {
        return arrayFrom(element.options)
            .filter(option => option.selected)
            .map(option => option.value);
    }

    /**
     * Make all the given options selected.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    makeOptionsSelected(options) {
        let selected = [];

        this.filterAvailable(options).forEach((option) => {
            if (option.options !== undefined) {
                selected = [...selected, ...this.makeOptionsSelected(option.options)];
            } else {
                selected.push(option.value);
            }
        });

        return [...this.props.selected, ...selected];
    }

    /**
     * Toggle a new set of selected elements.
     *
     * @param {Array} selected
     *
     * @returns {Array}
     */
    toggleSelected(selected) {
        const oldSelected = this.props.selected.slice(0);

        selected.forEach((value) => {
            const index = oldSelected.indexOf(value);

            if (index >= 0) {
                oldSelected.splice(index, 1);
            } else {
                oldSelected.push(value);
            }
        });

        return oldSelected;
    }

    /**
     * Filter options by a filtering function.
     *
     * @param {Array} options
     * @param {Function} filterer
     * @param {string} filterInput
     *
     * @returns {Array}
     */
    filterOptions(options, filterer, filterInput) {
        const { canFilter, filterCallback } = this.props;
        const filtered = [];

        options.forEach((option) => {
            if (option.options !== undefined) {
                const children = this.filterOptions(option.options, filterer, filterInput);

                if (children.length > 0) {
                    filtered.push({
                        label: option.label,
                        options: children,
                    });
                }
            } else if (filterer(option)) {
                // Test option against filter input
                if (canFilter && !filterCallback(option, filterInput)) {
                    return;
                }

                filtered.push(option);
            }
        });

        return filtered;
    }

    /**
     * Filter the available options.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    filterAvailable(options) {
        if (this.props.available !== undefined) {
            return this.filterOptions(
                options,
                option => (
                    this.props.available.indexOf(option.value) >= 0 &&
                    this.props.selected.indexOf(option.value) < 0
                ),
                this.state.filter.available,
            );
        }

        // Show all un-selected options
        return this.filterOptions(
            options,
            option => this.props.selected.indexOf(option.value) < 0,
            this.state.filter.available,
        );
    }

    /**
     * Filter the selected options.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    filterSelected(options) {
        if (this.props.preserveSelectOrder) {
            return this.filterSelectedByOrder(options);
        }

        // Order the selections by the default order
        return this.filterOptions(
            options,
            option => this.props.selected.indexOf(option.value) >= 0,
            this.state.filter.selected,
        );
    }

    /**
     * Preserve the selection order. This drops the opt-group associations.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    filterSelectedByOrder(options) {
        const { canFilter, filterCallback } = this.props;
        const labelMap = this.getLabelMap(options);

        const selectedOptions = this.props.selected.map(selected => ({
            value: selected,
            label: labelMap[selected],
        }));

        if (canFilter) {
            return selectedOptions.filter(
                selected => filterCallback(selected, this.state.filter.selected),
            );
        }

        return selectedOptions;
    }

    /**
     * @returns {Array}
     */
    renderOptions(options) {
        return options.map((option) => {
            const key = `${option.value}-${option.label}`;

            if (option.options !== undefined) {
                return (
                    <optgroup key={key} label={option.label}>
                        {this.renderOptions(option.options)}
                    </optgroup>
                );
            }

            return (
                <option key={key} value={option.value}>
                    {option.label}
                </option>
            );
        });
    }

    /**
     * @param {string} controlKey
     * @param {string} displayName
     * @param {Array} options
     * @param {function} ref
     * @param {React.Component} actions
     *
     * @returns {React.Component}
     */
    renderListBox(controlKey, displayName, options, ref, actions) {
        const {
            alignActions,
            canFilter,
            disabled,
            filterPlaceholder,
        } = this.props;

        return (
            <ListBox
                actions={alignActions === 'top' ? actions : null}
                canFilter={canFilter}
                controlKey={controlKey}
                disabled={disabled}
                displayName={displayName}
                filterPlaceholder={filterPlaceholder}
                filterValue={this.state.filter[controlKey]}
                id={this.id}
                inputRef={(c) => {
                    this[controlKey] = c;

                    if (ref) {
                        ref(c);
                    }
                }}
                onDoubleClick={this.onDoubleClick}
                onFilterChange={this.onFilterChange}
                onKeyUp={this.onKeyUp}
            >
                {options}
            </ListBox>
        );
    }

    /**
     * @returns {React.Component}
     */
    render() {
        const {
            alignActions,
            canFilter,
            disabled,
            name,
            options,
            selected,
            availableRef,
            selectedRef,
        } = this.props;
        const availableOptions = this.renderOptions(this.filterAvailable(options));
        const selectedOptions = this.renderOptions(this.filterSelected(options));
        const actionsRight = (

                <Action disabled={disabled} direction="right" isMoveAll onClick={this.onClick} />
                <Action disabled={disabled} direction="right" onClick={this.onClick} />

        );
        const actionsLeft = (

                <Action disabled={disabled} direction="left" onClick={this.onClick} />
                <Action disabled={disabled} direction="left" isMoveAll onClick={this.onClick} />

        );

        const className = classNames({
            'react-dual-listbox': true,
            'rdl-has-filter': canFilter,
            'rdl-align-top': alignActions === 'top',
        });

        return (

                {this.renderListBox('available', 'Available', availableOptions, availableRef, actionsRight)}
                {alignActions === 'middle' ? (

                        {actionsRight}
                        {actionsLeft}

                ) : null}
                {this.renderListBox('selected', 'Selected', selectedOptions, selectedRef, actionsLeft)}
                <input disabled={disabled} name={name} value={selected} type="hidden" />

        );
    }
}

export default DualListBox;
<commit_msg>Add support for returning options as objects rather than values<commit_after>import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import shortid from 'shortid';

import Action from './Action';
import arrayFrom from './arrayFrom';
import ListBox from './ListBox';

const optionShape = PropTypes.shape({
    value: PropTypes.any.isRequired,
    label: PropTypes.string.isRequired,
});
const valuePropType = PropTypes.arrayOf(
    PropTypes.oneOfType([
        PropTypes.string,
        optionShape,
    ]),
);

const defaultFilter = (option, filterInput) => {
    if (filterInput === '') {
        return true;
    }

    return (new RegExp(filterInput, 'i')).test(option.label);
};

class DualListBox extends React.Component {
    static propTypes = {
        options: PropTypes.arrayOf(
            PropTypes.oneOfType([
                optionShape,
                PropTypes.shape({
                    value: PropTypes.any,
                    options: PropTypes.arrayOf(optionShape),
                }),
            ]),
        ).isRequired,
        onChange: PropTypes.func.isRequired,

        alignActions: PropTypes.string,
        available: valuePropType,
        availableRef: PropTypes.func,
        canFilter: PropTypes.bool,
        disabled: PropTypes.bool,
        filter: PropTypes.shape({
            available: PropTypes.string.isRequired,
            selected: PropTypes.string.isRequired,
        }),
        filterCallback: PropTypes.func,
        filterPlaceholder: PropTypes.string,
        name: PropTypes.string,
        preserveSelectOrder: PropTypes.bool,
        selected: valuePropType,
        selectedRef: PropTypes.func,
        simpleValue: PropTypes.bool,
        onFilterChange: PropTypes.func,
    };

    static defaultProps = {
        alignActions: 'middle',
        available: undefined,
        availableRef: null,
        canFilter: false,
        disabled: false,
        filter: null,
        filterPlaceholder: 'Search...',
        filterCallback: defaultFilter,
        name: null,
        preserveSelectOrder: null,
        selected: [],
        selectedRef: null,
        simpleValue: true,
        onFilterChange: null,
    };

    /**
     * @param {Object} props
     *
     * @returns {void}
     */
    constructor(props) {
        super(props);

        this.state = {
            filter: props.filter ? props.filter : {
                available: '',
                selected: '',
            },
        };

        this.onClick = this.onClick.bind(this);
        this.onDoubleClick = this.onDoubleClick.bind(this);
        this.onKeyUp = this.onKeyUp.bind(this);
        this.onFilterChange = this.onFilterChange.bind(this);

        this.id = shortid.generate();
    }

    /**
     * @param {Object} filter
     *
     * @returns {void}
     */
    componentWillReceiveProps({ filter }) {
        if (filter !== null) {
            this.setState({ filter });
        }
    }

    /**
     * @param {Array} selected
     *
     * @returns {void}
     */
    onChange(selected) {
        const { options, simpleValue, onChange } = this.props;

        if (simpleValue) {
            onChange(selected);
        } else {
            onChange(options.filter(option => selected.indexOf(option.value) > -1));
        }
    }

    /**
     * @param {string} direction
     * @param {boolean} isMoveAll
     *
     * @returns {void}
     */
    onClick({ direction, isMoveAll }) {
        const { options } = this.props;
        const select = direction === 'right' ? this.available : this.selected;

        let selected = [];

        if (isMoveAll) {
            selected = direction === 'right' ? this.makeOptionsSelected(options) : [];
        } else {
            selected = this.toggleSelected(
                this.getSelectedOptions(select),
            );
        }

        this.onChange(selected);
    }

    /**
     * @param {Object} event
     *
     * @returns {void}
     */
    onDoubleClick(event) {
        const value = event.currentTarget.value;
        const selected = this.toggleSelected([value]);

        this.onChange(selected);
    }

    /**
     * @param {Event} event
     *
     * @returns {void}
     */
    onKeyUp(event) {
        const { currentTarget, key } = event;

        if (key === 'Enter') {
            const selected = this.toggleSelected(
                arrayFrom(currentTarget.options)
                    .filter(option => option.selected)
                    .map(option => option.value),
            );

            this.onChange(selected);
        }
    }

    /**
     * @param {Event} event
     *
     * @returns {void}
     */
    onFilterChange(event) {
        const { onFilterChange } = this.props;

        const newFilter = {
            ...this.state.filter,
            [event.target.dataset.key]: event.target.value,
        };

        if (onFilterChange) {
            onFilterChange(newFilter);
        } else {
            this.setState({ filter: newFilter });
        }
    }

    /**
     * @param {Array} options
     *
     * @returns {Array}
     */
    getFlatOptions(options) {
        const { simpleValue } = this.props;

        if (simpleValue) {
            return options;
        }

        return options.map(option => option.value);
    }

    /**
     * Converts a flat array to a key/value mapping.
     *
     * @param {Array} options
     *
     * @returns {Object}
     */
    getLabelMap(options) {
        let labelMap = {};

        options.forEach((option) => {
            if (option.options !== undefined) {
                labelMap = { ...labelMap, ...this.getLabelMap(option.options) };
            } else {
                labelMap[option.value] = option.label;
            }
        });

        return labelMap;
    }

    /**
     * Returns the selected options from a given element.
     *
     * @param {Object} element
     *
     * @returns {Array}
     */
    getSelectedOptions(element) {
        return arrayFrom(element.options)
            .filter(option => option.selected)
            .map(option => option.value);
    }

    /**
     * Make all the given options selected.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    makeOptionsSelected(options) {
        let selected = [];

        this.filterAvailable(options).forEach((option) => {
            if (option.options !== undefined) {
                selected = [...selected, ...this.makeOptionsSelected(option.options)];
            } else {
                selected.push(option.value);
            }
        });

        return [
            ...this.getFlatOptions(this.props.selected),
            ...selected,
        ];
    }

    /**
     * Toggle a new set of selected elements.
     *
     * @param {Array} selected
     *
     * @returns {Array}
     */
    toggleSelected(selected) {
        const oldSelected = this.getFlatOptions(this.props.selected).slice(0);

        selected.forEach((value) => {
            const index = oldSelected.indexOf(value);

            if (index >= 0) {
                oldSelected.splice(index, 1);
            } else {
                oldSelected.push(value);
            }
        });

        return oldSelected;
    }

    /**
     * Filter options by a filtering function.
     *
     * @param {Array} options
     * @param {Function} filterer
     * @param {string} filterInput
     *
     * @returns {Array}
     */
    filterOptions(options, filterer, filterInput) {
        const { canFilter, filterCallback } = this.props;
        const filtered = [];

        options.forEach((option) => {
            if (option.options !== undefined) {
                const children = this.filterOptions(option.options, filterer, filterInput);

                if (children.length > 0) {
                    filtered.push({
                        label: option.label,
                        options: children,
                    });
                }
            } else if (filterer(option)) {
                // Test option against filter input
                if (canFilter && !filterCallback(option, filterInput)) {
                    return;
                }

                filtered.push(option);
            }
        });

        return filtered;
    }

    /**
     * Filter the available options.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    filterAvailable(options) {
        if (this.props.available !== undefined) {
            return this.filterOptions(
                options,
                option => (
                    this.getFlatOptions(this.props.available).indexOf(option.value) >= 0 &&
                    this.getFlatOptions(this.props.selected).indexOf(option.value) < 0
                ),
                this.state.filter.available,
            );
        }

        // Show all un-selected options
        return this.filterOptions(
            options,
            option => this.getFlatOptions(this.props.selected).indexOf(option.value) < 0,
            this.state.filter.available,
        );
    }

    /**
     * Filter the selected options.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    filterSelected(options) {
        if (this.props.preserveSelectOrder) {
            return this.filterSelectedByOrder(options);
        }

        // Order the selections by the default order
        return this.filterOptions(
            options,
            option => this.getFlatOptions(this.props.selected).indexOf(option.value) >= 0,
            this.state.filter.selected,
        );
    }

    /**
     * Preserve the selection order. This drops the opt-group associations.
     *
     * @param {Array} options
     *
     * @returns {Array}
     */
    filterSelectedByOrder(options) {
        const { canFilter, filterCallback } = this.props;
        const labelMap = this.getLabelMap(options);

        const selectedOptions = this.getFlatOptions(this.props.selected).map(selected => ({
            value: selected,
            label: labelMap[selected],
        }));

        if (canFilter) {
            return selectedOptions.filter(
                selected => filterCallback(selected, this.state.filter.selected),
            );
        }

        return selectedOptions;
    }

    /**
     * @returns {Array}
     */
    renderOptions(options) {
        return options.map((option) => {
            const key = `${option.value}-${option.label}`;

            if (option.options !== undefined) {
                return (
                    <optgroup key={key} label={option.label}>
                        {this.renderOptions(option.options)}
                    </optgroup>
                );
            }

            return (
                <option key={key} value={option.value}>
                    {option.label}
                </option>
            );
        });
    }

    /**
     * @param {string} controlKey
     * @param {string} displayName
     * @param {Array} options
     * @param {function} ref
     * @param {React.Component} actions
     *
     * @returns {React.Component}
     */
    renderListBox(controlKey, displayName, options, ref, actions) {
        const {
            alignActions,
            canFilter,
            disabled,
            filterPlaceholder,
        } = this.props;

        return (
            <ListBox
                actions={alignActions === 'top' ? actions : null}
                canFilter={canFilter}
                controlKey={controlKey}
                disabled={disabled}
                displayName={displayName}
                filterPlaceholder={filterPlaceholder}
                filterValue={this.state.filter[controlKey]}
                id={this.id}
                inputRef={(c) => {
                    this[controlKey] = c;

                    if (ref) {
                        ref(c);
                    }
                }}
                onDoubleClick={this.onDoubleClick}
                onFilterChange={this.onFilterChange}
                onKeyUp={this.onKeyUp}
            >
                {options}
            </ListBox>
        );
    }

    /**
     * @returns {React.Component}
     */
    render() {
        const {
            alignActions,
            canFilter,
            disabled,
            name,
            options,
            selected,
            availableRef,
            selectedRef,
        } = this.props;
        const availableOptions = this.renderOptions(this.filterAvailable(options));
        const selectedOptions = this.renderOptions(this.filterSelected(options));
        const actionsRight = (

                <Action disabled={disabled} direction="right" isMoveAll onClick={this.onClick} />
                <Action disabled={disabled} direction="right" onClick={this.onClick} />

        );
        const actionsLeft = (

                <Action disabled={disabled} direction="left" onClick={this.onClick} />
                <Action disabled={disabled} direction="left" isMoveAll onClick={this.onClick} />

        );

        const className = classNames({
            'react-dual-listbox': true,
            'rdl-has-filter': canFilter,
            'rdl-align-top': alignActions === 'top',
        });
        const value = this.getFlatOptions(selected).join(',');

        return (

                {this.renderListBox('available', 'Available', availableOptions, availableRef, actionsRight)}
                {alignActions === 'middle' ? (

                        {actionsRight}
                        {actionsLeft}

                ) : null}
                {this.renderListBox('selected', 'Selected', selectedOptions, selectedRef, actionsLeft)}
                <input disabled={disabled} name={name} value={value} type="hidden" />

        );
    }
}

export default DualListBox;
<|endoftext|>


<commit_before>class AlertElement extends HTMLElement {
	/* Attributes to monitor */
	static get observedAttributes() { return ['level', 'dismiss', 'acknowledge', 'href']; }
	get level() { return this.getAttribute('level'); }
	set level(value) { return this.setAttribute('level', value); }
	get dismiss() { return this.getAttribute('dismiss'); }
	get acknowledge() { return this.getAttribute('acknowledge'); }
	get href() { return this.getAttribute('href'); }

	/* Lifecycle, element created */
	constructor() {
		super();
		this.includeCss();
	}

	/* Lifecycle, element appended to the DOM */
	connectedCallback() {
		this.setAttribute('role', 'alert');
		this.classList.add("joomla-alert--show");

		// Default to info
		if (!this.level || ['info', 'warning', 'danger', 'success'].indexOf(this.level) === -1) {
			this.setAttribute('level', 'info');
		}
		// Append button
		if (this.hasAttribute('dismiss') || this.hasAttribute('acknowledge') || (this.hasAttribute('href') && this.getAttribute('href') !== '')
			&& !this.querySelector('button.joomla-alert--close') && !this.querySelector('button.joomla-alert-button--close')) {
			this.appendCloseButton();
		}

		this.dispatchCustomEvent('joomla.alert.show');

		let closeButton = this.querySelector('button.joomla-alert--close') || this.querySelector('button.joomla-alert-button--close');

		if (closeButton) {
			closeButton.focus()
		}
	}

	/* Lifecycle, element removed from the DOM */
	disconnectedCallback() {
		this.removeEventListener('joomla.alert.show', this);
		this.removeEventListener('joomla.alert.close', this);
		this.removeEventListener('joomla.alert.closed', this);

		if (this.firstChild.tagName && this.firstChild.tagName.toLowerCase() === 'button') {
			this.firstChild.removeEventListener('click', this);
		}
	}

	/* Respond to attribute changes */
	attributeChangedCallback(attr, oldValue, newValue) {
		switch (attr) {
			case 'level':
				if (!newValue || (newValue && ['info', 'warning', 'danger', 'success'].indexOf(newValue) === -1)) {
					this.level = 'info';
				}
				break;
			case 'dismiss':
			case 'acknowledge':
				if (!newValue || newValue === "true") {
					this.appendCloseButton();
				} else {
					this.removeCloseButton();
				}
				break;
			case 'href':
				if (!newValue || newValue === '') {
					this.removeCloseButton();
				} else {
					if (!this.querySelector('button.joomla-alert-button--close')) {
						this.appendCloseButton();
					}
				}
				break;
		}
	}

	/* Method to close the alert */
	close() {
		this.dispatchCustomEvent('joomla.alert.close');
		this.addEventListener("transitionend", function () {
			this.dispatchCustomEvent('joomla.alert.closed');
			this.parentNode.removeChild(this);
		}, false);
		this.classList.remove('joomla-alert--show');
	}

	/* Method to dispatch events. Internal */
	dispatchCustomEvent(eventName) {
		let OriginalCustomEvent = new CustomEvent(eventName, { "bubbles": true, "cancelable": true });
		OriginalCustomEvent.relatedTarget = this;
		this.dispatchEvent(OriginalCustomEvent);
		this.removeEventListener(eventName, this);
	}

	/* Method to create the close button. Internal */
	appendCloseButton() {
		if (this.querySelector('button.joomla-alert--close') || this.querySelector('button.joomla-alert-button--close')) {
			return;
		}

		let self = this, closeButton = document.createElement('button');

		if (this.hasAttribute('dismiss')) {
			closeButton.classList.add('joomla-alert--close');
			closeButton.innerHTML = '×';
			closeButton.setAttribute('aria-label', this.getText('JCLOSE', 'Close'));
		} else {
			closeButton.classList.add('joomla-alert-button--close');
			if (this.hasAttribute('acknowledge')) {
				closeButton.innerHTML = this.getText('JOK', 'ok');
			} else {
				closeButton.innerHTML = this.getText('JOPEN', 'Open');
			}
		}

		if (this.firstChild) {
			this.insertBefore(closeButton, this.firstChild);
		} else {
			this.appendChild(closeButton);
		}

		/* Add the required listener */
		if (closeButton) {
			closeButton.addEventListener('click', function () {
				self.dispatchCustomEvent('joomla.alert.buttonClicked');
				if (self.href) {
					window.location.href = self.href;
				}
				self.close();
			});
		}

		if (this.hasAttribute('auto-dismiss')) {
			setTimeout(function () {
				self.dispatchCustomEvent('joomla.alert.buttonClicked');
				if (self.href) {
					window.location.href = self.href;
				}
				self.close();
			}, parseInt(self.getAttribute('auto-dismiss')) ? self.getAttribute('auto-dismiss') : 3000);
		}
	}

	/* Method to remove the close button. Internal */
	removeCloseButton() {
		let button = this.querySelector('button');
		if (button) {
			button.removeEventListener('click', this);
			button.parentNode.removeChild(button);
		}
	}

	/* Method to get the translated text. Internal */
	getText(str, fallback) {
		return (window.Joomla && Joomla.JText && Joomla.JText._ && typeof Joomla.JText._ === 'function' && Joomla.JText._(str)) ? Joomla.JText._(str) : fallback;
	}

	includeCss() {
		if (!document.getElementById('joomla-alert-stylesheet')) {
			const style = document.createElement('style');
			style.id = 'joomla-alert-stylesheet';
			style.innerHTML = `{{stylesheet}}`;
			document.head.appendChild(style);
		}
	}
}

customElements.define('joomla-alert', AlertElement);
<commit_msg>Lint Alert<commit_after>class AlertElement extends HTMLElement {
  /* Attributes to monitor */
  static get observedAttributes() { return ['level', 'dismiss', 'acknowledge', 'href']; }
  get level() { return this.getAttribute('level'); }
  set level(value) { return this.setAttribute('level', value); }
  get dismiss() { return this.getAttribute('dismiss'); }
  set dismiss(value) { return this.setAttribute('dismiss', value); }
  get acknowledge() { return this.getAttribute('acknowledge'); }
  set acknowledge(value) { return this.setAttribute('acknowledge', value); }
  get href() { return this.getAttribute('href'); }
  set href(value) { return this.setAttribute('href', value); }

  /* Lifecycle, element created */
  constructor() {
    super();

    /** Include the relative styles */
    if (!document.getElementById('joomla-alert-stylesheet')) {
      const style = document.createElement('style');
      style.id = 'joomla-alert-stylesheet';
      style.innerHTML = '{{stylesheet}}';
      document.head.appendChild(style);
    }
  }

  /* Lifecycle, element appended to the DOM */
  connectedCallback() {
    this.setAttribute('role', 'alert');
    this.classList.add('joomla-alert--show');

    // Default to info
    if (!this.level || ['info', 'warning', 'danger', 'success'].indexOf(this.level) === -1) {
      this.setAttribute('level', 'info');
    }
    // Append button
    if (this.hasAttribute('dismiss') || this.hasAttribute('acknowledge') || (this.hasAttribute('href') && this.getAttribute('href') !== '')) {
      if (!this.querySelector('button.joomla-alert--close') && !this.querySelector('button.joomla-alert-button--close')) {
        this.appendCloseButton();
      }
    }

    this.dispatchCustomEvent('joomla.alert.show');

    const closeButton = this.querySelector('button.joomla-alert--close') || this.querySelector('button.joomla-alert-button--close');

    if (closeButton) {
      closeButton.focus();
    }
  }

  /* Lifecycle, element removed from the DOM */
  disconnectedCallback() {
    this.removeEventListener('joomla.alert.show', this);
    this.removeEventListener('joomla.alert.close', this);
    this.removeEventListener('joomla.alert.closed', this);

    if (this.firstChild.tagName && this.firstChild.tagName.toLowerCase() === 'button') {
      this.firstChild.removeEventListener('click', this);
    }
  }

  /* Respond to attribute changes */
  attributeChangedCallback(attr, oldValue, newValue) {
    switch (attr) {
      case 'level':
        if (!newValue || (newValue && ['info', 'warning', 'danger', 'success'].indexOf(newValue) === -1)) {
          this.level = 'info';
        }
        break;
      case 'dismiss':
      case 'acknowledge':
        if (!newValue || newValue === 'true') {
          this.appendCloseButton();
        } else {
          this.removeCloseButton();
        }
        break;
      case 'href':
        if (!newValue || newValue === '') {
          this.removeCloseButton();
        } else if (!this.querySelector('button.joomla-alert-button--close')) {
          this.appendCloseButton();
        }
        break;
      default:
        break;
    }
  }

  /* Method to close the alert */
  close() {
    this.dispatchCustomEvent('joomla.alert.close');
    const fireEnd = () => {
      this.dispatchCustomEvent('joomla.alert.closed');
      this.parentNode.removeChild(this);
    };

    this.addEventListener('transitionend', fireEnd);
    this.classList.remove('joomla-alert--show');
  }

  /* Method to dispatch events. Internal */
  dispatchCustomEvent(eventName) {
    const OriginalCustomEvent = new CustomEvent(eventName, { bubbles: true, cancelable: true });
    OriginalCustomEvent.relatedTarget = this;
    this.dispatchEvent(OriginalCustomEvent);
    this.removeEventListener(eventName, this);
  }

  /* Method to create the close button. Internal */
  appendCloseButton() {
    if (this.querySelector('button.joomla-alert--close') || this.querySelector('button.joomla-alert-button--close')) {
      return;
    }

    const self = this;
    const closeButton = document.createElement('button');

    if (this.hasAttribute('dismiss')) {
      closeButton.classList.add('joomla-alert--close');
      closeButton.innerHTML = '×';
      closeButton.setAttribute('aria-label', this.getText('JCLOSE', 'Close'));
    } else {
      closeButton.classList.add('joomla-alert-button--close');
      if (this.hasAttribute('acknowledge')) {
        closeButton.innerHTML = this.getText('JOK', 'ok');
      } else {
        closeButton.innerHTML = this.getText('JOPEN', 'Open');
      }
    }

    if (this.firstChild) {
      this.insertBefore(closeButton, this.firstChild);
    } else {
      this.appendChild(closeButton);
    }

    /* Add the required listener */
    if (closeButton) {
      closeButton.addEventListener('click', () => {
        self.dispatchCustomEvent('joomla.alert.buttonClicked');
        if (self.href) {
          window.location.href = self.href;
        }
        self.close();
      });
    }

    if (this.hasAttribute('auto-dismiss')) {
      setTimeout(() => {
        self.dispatchCustomEvent('joomla.alert.buttonClicked');
        if (self.href) {
          window.location.href = self.href;
        }
        self.close();
      }, parseInt(self.getAttribute('auto-dismiss'), 50));
    }
  }

  /* Method to remove the close button. Internal */
  removeCloseButton() {
    const button = this.querySelector('button');
    if (button) {
      button.removeEventListener('click', this);
      button.parentNode.removeChild(button);
    }
  }

  /* Method to get the translated text. Internal */
  /*eslint-disable */
  getText(str, fallback) {
    return (window.Joomla && Joomla.JText && Joomla.JText._ && typeof Joomla.JText._ === 'function' && Joomla.JText._(str)) ? Joomla.JText._(str) : fallback;
  }
  /*eslint-enable */
}

customElements.define('joomla-alert', AlertElement);
<|endoftext|>


<commit_before>
'use strict';

import MD5 from 'MD5';

class StyleMeService {

	constructor () {

	}

}

export default new StyleMeService();<commit_msg>Create style me class<commit_after>
'use strict';

import MD5 from 'MD5';

class StyleMe {

	constructor () {

		this.configuration = null;

		this.documentHead = document.getElementsByTagName('head')[0];
		this.appAttribute = 'data-style-me';

		this.originalStylesheets = Array.prototype.filter.call(document.styleSheets, stylesheet => {
			return !stylesheet['ownerNode'].hasAttribute(this.appAttribute);
		}).reduce((collection, stylesheet) => {

			if (typeof stylesheet.href === 'string') {

				let	stylesheetKey = MD5(stylesheet['ownerNode'].getAttribute('href'));

				console.log(stylesheet['ownerNode'].getAttribute('href'));

				collection[stylesheetKey] = stylesheet['ownerNode'];
			}

			return collection;

		}, {});

		this.injectedStyleSheets = {};

		this.autoUpdateIntervals = {};

	}

	applyConfiguration (configuration) {

		console.log('applyConfiguration', configuration);

		this.configuration = configuration;

		this.processConfiguration();

	}

	processConfiguration () {

		let configuration = this.configuration;

		if (!(Object.prototype.toString.call(configuration).slice(8, -1) === 'Object')) {
			return;
		}

		let {enable} = configuration;

		this.destroy();

		if (enable) {
			this.init();
		}

	}

	removeStylesheets () {

		let injectedStyleSheets = this.injectedStyleSheets;

		let originalStyleSheets = this.originalStylesheets;

		for (let stylesheet in injectedStyleSheets) {
			if (injectedStyleSheets.hasOwnProperty(stylesheet)) {

				let injectedStylesheet = injectedStyleSheets[stylesheet].node;

				if (injectedStylesheet && injectedStylesheet.parentNode !== null) {

					// remove injected stylesheets from document and delete all stored links to them
					injectedStylesheet.parentNode.removeChild(injectedStylesheet);
					delete injectedStyleSheets[stylesheet];

				}
			}
		}

		for (let styleSheetKey in originalStyleSheets) {
			if (originalStyleSheets.hasOwnProperty(styleSheetKey)) {
				this.toggleOriginalStylesheet(styleSheetKey, true);
			}
		}

	}

	createStylesheets () {

		let {styleSheets} = this.configuration;

		styleSheets.forEach(styleSheet => {

			// create new styleSheet
			let newStylesheet = document.createElement('link');

			newStylesheet.setAttribute('rel', 'stylesheet');
			newStylesheet.setAttribute('type', 'text/css');
			newStylesheet.setAttribute(this.appAttribute, 'true');
			newStylesheet.setAttribute('href', styleSheet.src);

			this.documentHead.appendChild(newStylesheet);
			this.injectedStyleSheets[styleSheet.key] = {
				node: newStylesheet,
				src: styleSheet.src
			};

			if (styleSheet.overrideOriginal === true) {
				this.toggleOriginalStylesheet(MD5(styleSheet.src), false);
			}

		});

	}

	toggleOriginalStylesheet (styleSheetKey, enable) {

		let originalStyleSheets = this.originalStylesheets;

		if (originalStyleSheets.hasOwnProperty(styleSheetKey)) {

			if (enable) {
				originalStyleSheets[styleSheetKey].removeAttribute('disabled');
			} else {
				originalStyleSheets[styleSheetKey].setAttribute('disabled', 'disabled');
			}

		}

	}

	init () {

		this.createStylesheets();

		this.processAutoUpdate();

	}

	processAutoUpdate () {

		let {enable, autoUpdate} = this.configuration;

		let enableAutoUpdate = enable && autoUpdate;

		let injectedStyleSheets = this.injectedStyleSheets;

		for (let styleSheetKey in injectedStyleSheets) {
			if (injectedStyleSheets.hasOwnProperty(styleSheetKey)) {

				this.handleAutoUpdateInterval(styleSheetKey, enableAutoUpdate);

			}
		}

	}

	handleAutoUpdateInterval (styleSheetKey, enable) {

		let autoUpdateIntervals = this.autoUpdateIntervals;
		let injectedStyleSheets = this.injectedStyleSheets;
		let {updateFrequency} = this.configuration;

		if (autoUpdateIntervals.hasOwnProperty(styleSheetKey)) {

			window.clearInterval(autoUpdateIntervals[styleSheetKey]);

		}

		if (enable === true) {

			let updateIntervalValue = parseInt(updateFrequency, 10) * 1000;

			autoUpdateIntervals[styleSheetKey] = window.setInterval(() => {

				let injectedStyleSheetData = injectedStyleSheets[styleSheetKey];

				let injectedStyleSheet = injectedStyleSheetData.node;

				let timestamp = (!!~injectedStyleSheetData.src.indexOf('?') ? '&' : '?') + 'timestamp=' + (new Date()).getTime();

				injectedStyleSheet.setAttribute('href', injectedStyleSheetData.src + timestamp);

			}, updateIntervalValue);
		}

	}

	removeAutoUpdateIntervals () {

		let autoUpdateIntervals = this.autoUpdateIntervals;

		for (let styleSheetKey in autoUpdateIntervals) {
			if (autoUpdateIntervals.hasOwnProperty(styleSheetKey)) {
				this.handleAutoUpdateInterval(styleSheetKey, false);
			}
		}

	}

	destroy () {

		this.removeAutoUpdateIntervals();

		this.removeStylesheets();

	}

}

export default new StyleMe();<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
 /* eslint-env node */
'use strict';

var SDPUtils = require('sdp');

// Wraps the peerconnection event eventNameToWrap in a function
// which returns the modified event object.
function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
  if (!window.RTCPeerConnection) {
    return;
  }
  var proto = window.RTCPeerConnection.prototype;
  var nativeAddEventListener = proto.addEventListener;
  proto.addEventListener = function(nativeEventName, cb) {
    if (nativeEventName !== eventNameToWrap) {
      return nativeAddEventListener.apply(this, arguments);
    }
    var wrappedCallback = function(e) {
      cb(wrapper(e));
    };
    this._eventMap = this._eventMap || {};
    this._eventMap[cb] = wrappedCallback;
    return nativeAddEventListener.apply(this, [nativeEventName,
      wrappedCallback]);
  };

  var nativeRemoveEventListener = proto.removeEventListener;
  proto.removeEventListener = function(nativeEventName, cb) {
    if (nativeEventName !== eventNameToWrap || !this._eventMap
        || !this._eventMap[cb]) {
      return nativeRemoveEventListener.apply(this, arguments);
    }
    var unwrappedCb = this._eventMap[cb];
    delete this._eventMap[cb];
    return nativeRemoveEventListener.apply(this, [nativeEventName,
      unwrappedCb]);
  };

  Object.defineProperty(proto, 'on' + eventNameToWrap, {
    get: function() {
      return this['_on' + eventNameToWrap];
    },
    set: function(cb) {
      if (this['_on' + eventNameToWrap]) {
        this.removeEventListener(eventNameToWrap,
            this['_on' + eventNameToWrap]);
      }
      this.addEventListener(eventNameToWrap,
          this['_on' + eventNameToWrap] = cb);
    }
  });
}

module.exports = {
  shimRTCIceCandidate: function(window) {
    // foundation is arbitrarily chosen as an indicator for full support for
    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
    if (window.RTCIceCandidate && 'foundation' in
        window.RTCIceCandidate.prototype) {
      return;
    }

    var NativeRTCIceCandidate = window.RTCIceCandidate;
    window.RTCIceCandidate = function(args) {
      // Remove the a= which shouldn't be part of the candidate string.
      if (typeof args === 'object' && args.candidate &&
          args.candidate.indexOf('a=') === 0) {
        args = JSON.parse(JSON.stringify(args));
        args.candidate = args.candidate.substr(2);
      }

      // Augment the native candidate with the parsed fields.
      var nativeCandidate = new NativeRTCIceCandidate(args);
      var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
      var augmentedCandidate = Object.assign(nativeCandidate,
          parsedCandidate);

      // Add a serializer that does not serialize the extra attributes.
      augmentedCandidate.toJSON = function() {
        return {
          candidate: augmentedCandidate.candidate,
          sdpMid: augmentedCandidate.sdpMid,
          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
          usernameFragment: augmentedCandidate.usernameFragment,
        };
      };
      return augmentedCandidate;
    };

    // Hook up the augmented candidate in onicecandidate and
    // addEventListener('icecandidate', ...)
    wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
      if (e.candidate) {
        Object.defineProperty(e, 'candidate', {
          value: new window.RTCIceCandidate(e.candidate),
          writable: 'false'
        });
      }
      return e;
    });
  }
};
<commit_msg>common: handle onxxx = null<commit_after>/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
 /* eslint-env node */
'use strict';

var SDPUtils = require('sdp');

// Wraps the peerconnection event eventNameToWrap in a function
// which returns the modified event object.
function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
  if (!window.RTCPeerConnection) {
    return;
  }
  var proto = window.RTCPeerConnection.prototype;
  var nativeAddEventListener = proto.addEventListener;
  proto.addEventListener = function(nativeEventName, cb) {
    if (nativeEventName !== eventNameToWrap) {
      return nativeAddEventListener.apply(this, arguments);
    }
    var wrappedCallback = function(e) {
      cb(wrapper(e));
    };
    this._eventMap = this._eventMap || {};
    this._eventMap[cb] = wrappedCallback;
    return nativeAddEventListener.apply(this, [nativeEventName,
      wrappedCallback]);
  };

  var nativeRemoveEventListener = proto.removeEventListener;
  proto.removeEventListener = function(nativeEventName, cb) {
    if (nativeEventName !== eventNameToWrap || !this._eventMap
        || !this._eventMap[cb]) {
      return nativeRemoveEventListener.apply(this, arguments);
    }
    var unwrappedCb = this._eventMap[cb];
    delete this._eventMap[cb];
    return nativeRemoveEventListener.apply(this, [nativeEventName,
      unwrappedCb]);
  };

  Object.defineProperty(proto, 'on' + eventNameToWrap, {
    get: function() {
      return this['_on' + eventNameToWrap];
    },
    set: function(cb) {
      if (this['_on' + eventNameToWrap]) {
        this.removeEventListener(eventNameToWrap,
            this['_on' + eventNameToWrap]);
        delete this['_on' + eventNameToWrap];
      }
      if (cb) {
        this.addEventListener(eventNameToWrap,
            this['_on' + eventNameToWrap] = cb);
      }
    }
  });
}

module.exports = {
  shimRTCIceCandidate: function(window) {
    // foundation is arbitrarily chosen as an indicator for full support for
    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
    if (window.RTCIceCandidate && 'foundation' in
        window.RTCIceCandidate.prototype) {
      return;
    }

    var NativeRTCIceCandidate = window.RTCIceCandidate;
    window.RTCIceCandidate = function(args) {
      // Remove the a= which shouldn't be part of the candidate string.
      if (typeof args === 'object' && args.candidate &&
          args.candidate.indexOf('a=') === 0) {
        args = JSON.parse(JSON.stringify(args));
        args.candidate = args.candidate.substr(2);
      }

      // Augment the native candidate with the parsed fields.
      var nativeCandidate = new NativeRTCIceCandidate(args);
      var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
      var augmentedCandidate = Object.assign(nativeCandidate,
          parsedCandidate);

      // Add a serializer that does not serialize the extra attributes.
      augmentedCandidate.toJSON = function() {
        return {
          candidate: augmentedCandidate.candidate,
          sdpMid: augmentedCandidate.sdpMid,
          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
          usernameFragment: augmentedCandidate.usernameFragment,
        };
      };
      return augmentedCandidate;
    };

    // Hook up the augmented candidate in onicecandidate and
    // addEventListener('icecandidate', ...)
    wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
      if (e.candidate) {
        Object.defineProperty(e, 'candidate', {
          value: new window.RTCIceCandidate(e.candidate),
          writable: 'false'
        });
      }
      return e;
    });
  }
};
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
/* eslint-env node */
'use strict';

import SDPUtils from 'sdp';
import * as utils from './utils';

export function shimRTCIceCandidate(window) {
  // foundation is arbitrarily chosen as an indicator for full support for
  // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
  if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in
      window.RTCIceCandidate.prototype)) {
    return;
  }

  const NativeRTCIceCandidate = window.RTCIceCandidate;
  window.RTCIceCandidate = function(args) {
    // Remove the a= which shouldn't be part of the candidate string.
    if (typeof args === 'object' && args.candidate &&
        args.candidate.indexOf('a=') === 0) {
      args = JSON.parse(JSON.stringify(args));
      args.candidate = args.candidate.substr(2);
    }

    if (args.candidate && args.candidate.length) {
      // Augment the native candidate with the parsed fields.
      const nativeCandidate = new NativeRTCIceCandidate(args);
      const parsedCandidate = SDPUtils.parseCandidate(args.candidate);
      const augmentedCandidate = Object.assign(nativeCandidate,
          parsedCandidate);

      // Add a serializer that does not serialize the extra attributes.
      augmentedCandidate.toJSON = function() {
        return {
          candidate: augmentedCandidate.candidate,
          sdpMid: augmentedCandidate.sdpMid,
          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
          usernameFragment: augmentedCandidate.usernameFragment,
        };
      };
      return augmentedCandidate;
    }
    return new NativeRTCIceCandidate(args);
  };
  window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;

  // Hook up the augmented candidate in onicecandidate and
  // addEventListener('icecandidate', ...)
  utils.wrapPeerConnectionEvent(window, 'icecandidate', e => {
    if (e.candidate) {
      Object.defineProperty(e, 'candidate', {
        value: new window.RTCIceCandidate(e.candidate),
        writable: 'false'
      });
    }
    return e;
  });
}

export function shimMaxMessageSize(window) {
  if (window.RTCSctpTransport || !window.RTCPeerConnection) {
    return;
  }
  const browserDetails = utils.detectBrowser(window);

  if (!('sctp' in window.RTCPeerConnection.prototype)) {
    Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
      get() {
        return typeof this._sctp === 'undefined' ? null : this._sctp;
      }
    });
  }

  const sctpInDescription = function(description) {
    const sections = SDPUtils.splitSections(description.sdp);
    sections.shift();
    return sections.some(mediaSection => {
      const mLine = SDPUtils.parseMLine(mediaSection);
      return mLine && mLine.kind === 'application'
          && mLine.protocol.indexOf('SCTP') !== -1;
    });
  };

  const getRemoteFirefoxVersion = function(description) {
    // TODO: Is there a better solution for detecting Firefox?
    const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
    if (match === null || match.length < 2) {
      return -1;
    }
    const version = parseInt(match[1], 10);
    // Test for NaN (yes, this is ugly)
    return version !== version ? -1 : version;
  };

  const getCanSendMaxMessageSize = function(remoteIsFirefox) {
    // Every implementation we know can send at least 64 KiB.
    // Note: Although Chrome is technically able to send up to 256 KiB, the
    //       data does not reach the other peer reliably.
    //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
    let canSendMaxMessageSize = 65536;
    if (browserDetails.browser === 'firefox') {
      if (browserDetails.version < 57) {
        if (remoteIsFirefox === -1) {
          // FF < 57 will send in 16 KiB chunks using the deprecated PPID
          // fragmentation.
          canSendMaxMessageSize = 16384;
        } else {
          // However, other FF (and RAWRTC) can reassemble PPID-fragmented
          // messages. Thus, supporting ~2 GiB when sending.
          canSendMaxMessageSize = 2147483637;
        }
      } else if (browserDetails.version < 60) {
        // Currently, all FF >= 57 will reset the remote maximum message size
        // to the default value when a data channel is created at a later
        // stage. :(
        // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
        canSendMaxMessageSize =
          browserDetails.version === 57 ? 65535 : 65536;
      } else {
        // FF >= 60 supports sending ~2 GiB
        canSendMaxMessageSize = 2147483637;
      }
    }
    return canSendMaxMessageSize;
  };

  const getMaxMessageSize = function(description, remoteIsFirefox) {
    // Note: 65536 bytes is the default value from the SDP spec. Also,
    //       every implementation we know supports receiving 65536 bytes.
    let maxMessageSize = 65536;

    // FF 57 has a slightly incorrect default remote max message size, so
    // we need to adjust it here to avoid a failure when sending.
    // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
    if (browserDetails.browser === 'firefox'
         && browserDetails.version === 57) {
      maxMessageSize = 65535;
    }

    const match = SDPUtils.matchPrefix(description.sdp,
      'a=max-message-size:');
    if (match.length > 0) {
      maxMessageSize = parseInt(match[0].substr(19), 10);
    } else if (browserDetails.browser === 'firefox' &&
                remoteIsFirefox !== -1) {
      // If the maximum message size is not present in the remote SDP and
      // both local and remote are Firefox, the remote peer can receive
      // ~2 GiB.
      maxMessageSize = 2147483637;
    }
    return maxMessageSize;
  };

  const origSetRemoteDescription =
      window.RTCPeerConnection.prototype.setRemoteDescription;
  window.RTCPeerConnection.prototype.setRemoteDescription = function() {
    this._sctp = null;

    if (sctpInDescription(arguments[0])) {
      // Check if the remote is FF.
      const isFirefox = getRemoteFirefoxVersion(arguments[0]);

      // Get the maximum message size the local peer is capable of sending
      const canSendMMS = getCanSendMaxMessageSize(isFirefox);

      // Get the maximum message size of the remote peer.
      const remoteMMS = getMaxMessageSize(arguments[0], isFirefox);

      // Determine final maximum message size
      let maxMessageSize;
      if (canSendMMS === 0 && remoteMMS === 0) {
        maxMessageSize = Number.POSITIVE_INFINITY;
      } else if (canSendMMS === 0 || remoteMMS === 0) {
        maxMessageSize = Math.max(canSendMMS, remoteMMS);
      } else {
        maxMessageSize = Math.min(canSendMMS, remoteMMS);
      }

      // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
      // attribute.
      const sctp = {};
      Object.defineProperty(sctp, 'maxMessageSize', {
        get() {
          return maxMessageSize;
        }
      });
      this._sctp = sctp;
    }

    return origSetRemoteDescription.apply(this, arguments);
  };
}

export function shimSendThrowTypeError(window) {
  if (!(window.RTCPeerConnection &&
      'createDataChannel' in window.RTCPeerConnection.prototype)) {
    return;
  }

  // Note: Although Firefox >= 57 has a native implementation, the maximum
  //       message size can be reset for all data channels at a later stage.
  //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831

  function wrapDcSend(dc, pc) {
    const origDataChannelSend = dc.send;
    dc.send = function() {
      const data = arguments[0];
      const length = data.length || data.size || data.byteLength;
      if (dc.readyState === 'open' &&
          pc.sctp && length > pc.sctp.maxMessageSize) {
        throw new TypeError('Message too large (can send a maximum of ' +
          pc.sctp.maxMessageSize + ' bytes)');
      }
      return origDataChannelSend.apply(dc, arguments);
    };
  }
  const origCreateDataChannel =
    window.RTCPeerConnection.prototype.createDataChannel;
  window.RTCPeerConnection.prototype.createDataChannel = function() {
    const dataChannel = origCreateDataChannel.apply(this, arguments);
    wrapDcSend(dataChannel, this);
    return dataChannel;
  };
  utils.wrapPeerConnectionEvent(window, 'datachannel', e => {
    wrapDcSend(e.channel, e.target);
    return e;
  });
}

/* shims RTCConnectionState by pretending it is the same as iceConnectionState.
 * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
 * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect
 * since DTLS failures would be hidden. See
 * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
 * for the Firefox tracking bug.
 */
export function shimConnectionState(window) {
  if (!window.RTCPeerConnection ||
      'connectionState' in window.RTCPeerConnection.prototype) {
    return;
  }
  const proto = window.RTCPeerConnection.prototype;
  Object.defineProperty(proto, 'connectionState', {
    get() {
      return {
        completed: 'connected',
        checking: 'connecting'
      }[this.iceConnectionState] || this.iceConnectionState;
    },
    enumerable: true,
    configurable: true
  });
  Object.defineProperty(proto, 'onconnectionstatechange', {
    get() {
      return this._onconnectionstatechange || null;
    },
    set(cb) {
      if (this._onconnectionstatechange) {
        this.removeEventListener('connectionstatechange',
            this._onconnectionstatechange);
        delete this._onconnectionstatechange;
      }
      if (cb) {
        this.addEventListener('connectionstatechange',
            this._onconnectionstatechange = cb);
      }
    },
    enumerable: true,
    configurable: true
  });

  ['setLocalDescription', 'setRemoteDescription'].forEach((method) => {
    const origMethod = proto[method];
    proto[method] = function() {
      if (!this._connectionstatechangepoly) {
        this._connectionstatechangepoly = e => {
          const pc = e.target;
          if (pc._lastConnectionState !== pc.connectionState) {
            pc._lastConnectionState = pc.connectionState;
            const newEvent = new Event('connectionstatechange', e);
            pc.dispatchEvent(newEvent);
          }
          return e;
        };
        this.addEventListener('iceconnectionstatechange',
          this._connectionstatechangepoly);
      }
      return origMethod.apply(this, arguments);
    };
  });
}

export function removeAllowExtmapMixed(window) {
  /* remove a=extmap-allow-mixed for Chrome < M71 */
  if (!window.RTCPeerConnection) {
    return;
  }
  const browserDetails = utils.detectBrowser(window);
  if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) {
    return;
  }
  const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription;
  window.RTCPeerConnection.prototype.setRemoteDescription = function(desc) {
    if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) {
      desc.sdp = desc.sdp.split('\n').filter((line) => {
        return line.trim() !== 'a=extmap-allow-mixed';
      }).join('\n');
    }
    return nativeSRD.apply(this, arguments);
  };
}
<commit_msg>Fix sctpInDescription shim to not TypeError on rollback.<commit_after>/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
/* eslint-env node */
'use strict';

import SDPUtils from 'sdp';
import * as utils from './utils';

export function shimRTCIceCandidate(window) {
  // foundation is arbitrarily chosen as an indicator for full support for
  // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
  if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in
      window.RTCIceCandidate.prototype)) {
    return;
  }

  const NativeRTCIceCandidate = window.RTCIceCandidate;
  window.RTCIceCandidate = function(args) {
    // Remove the a= which shouldn't be part of the candidate string.
    if (typeof args === 'object' && args.candidate &&
        args.candidate.indexOf('a=') === 0) {
      args = JSON.parse(JSON.stringify(args));
      args.candidate = args.candidate.substr(2);
    }

    if (args.candidate && args.candidate.length) {
      // Augment the native candidate with the parsed fields.
      const nativeCandidate = new NativeRTCIceCandidate(args);
      const parsedCandidate = SDPUtils.parseCandidate(args.candidate);
      const augmentedCandidate = Object.assign(nativeCandidate,
          parsedCandidate);

      // Add a serializer that does not serialize the extra attributes.
      augmentedCandidate.toJSON = function() {
        return {
          candidate: augmentedCandidate.candidate,
          sdpMid: augmentedCandidate.sdpMid,
          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
          usernameFragment: augmentedCandidate.usernameFragment,
        };
      };
      return augmentedCandidate;
    }
    return new NativeRTCIceCandidate(args);
  };
  window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;

  // Hook up the augmented candidate in onicecandidate and
  // addEventListener('icecandidate', ...)
  utils.wrapPeerConnectionEvent(window, 'icecandidate', e => {
    if (e.candidate) {
      Object.defineProperty(e, 'candidate', {
        value: new window.RTCIceCandidate(e.candidate),
        writable: 'false'
      });
    }
    return e;
  });
}

export function shimMaxMessageSize(window) {
  if (window.RTCSctpTransport || !window.RTCPeerConnection) {
    return;
  }
  const browserDetails = utils.detectBrowser(window);

  if (!('sctp' in window.RTCPeerConnection.prototype)) {
    Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
      get() {
        return typeof this._sctp === 'undefined' ? null : this._sctp;
      }
    });
  }

  const sctpInDescription = function(description) {
    if (!description || !description.sdp) {
      return false;
    }
    const sections = SDPUtils.splitSections(description.sdp);
    sections.shift();
    return sections.some(mediaSection => {
      const mLine = SDPUtils.parseMLine(mediaSection);
      return mLine && mLine.kind === 'application'
          && mLine.protocol.indexOf('SCTP') !== -1;
    });
  };

  const getRemoteFirefoxVersion = function(description) {
    // TODO: Is there a better solution for detecting Firefox?
    const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
    if (match === null || match.length < 2) {
      return -1;
    }
    const version = parseInt(match[1], 10);
    // Test for NaN (yes, this is ugly)
    return version !== version ? -1 : version;
  };

  const getCanSendMaxMessageSize = function(remoteIsFirefox) {
    // Every implementation we know can send at least 64 KiB.
    // Note: Although Chrome is technically able to send up to 256 KiB, the
    //       data does not reach the other peer reliably.
    //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
    let canSendMaxMessageSize = 65536;
    if (browserDetails.browser === 'firefox') {
      if (browserDetails.version < 57) {
        if (remoteIsFirefox === -1) {
          // FF < 57 will send in 16 KiB chunks using the deprecated PPID
          // fragmentation.
          canSendMaxMessageSize = 16384;
        } else {
          // However, other FF (and RAWRTC) can reassemble PPID-fragmented
          // messages. Thus, supporting ~2 GiB when sending.
          canSendMaxMessageSize = 2147483637;
        }
      } else if (browserDetails.version < 60) {
        // Currently, all FF >= 57 will reset the remote maximum message size
        // to the default value when a data channel is created at a later
        // stage. :(
        // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
        canSendMaxMessageSize =
          browserDetails.version === 57 ? 65535 : 65536;
      } else {
        // FF >= 60 supports sending ~2 GiB
        canSendMaxMessageSize = 2147483637;
      }
    }
    return canSendMaxMessageSize;
  };

  const getMaxMessageSize = function(description, remoteIsFirefox) {
    // Note: 65536 bytes is the default value from the SDP spec. Also,
    //       every implementation we know supports receiving 65536 bytes.
    let maxMessageSize = 65536;

    // FF 57 has a slightly incorrect default remote max message size, so
    // we need to adjust it here to avoid a failure when sending.
    // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
    if (browserDetails.browser === 'firefox'
         && browserDetails.version === 57) {
      maxMessageSize = 65535;
    }

    const match = SDPUtils.matchPrefix(description.sdp,
      'a=max-message-size:');
    if (match.length > 0) {
      maxMessageSize = parseInt(match[0].substr(19), 10);
    } else if (browserDetails.browser === 'firefox' &&
                remoteIsFirefox !== -1) {
      // If the maximum message size is not present in the remote SDP and
      // both local and remote are Firefox, the remote peer can receive
      // ~2 GiB.
      maxMessageSize = 2147483637;
    }
    return maxMessageSize;
  };

  const origSetRemoteDescription =
      window.RTCPeerConnection.prototype.setRemoteDescription;
  window.RTCPeerConnection.prototype.setRemoteDescription = function() {
    this._sctp = null;

    if (sctpInDescription(arguments[0])) {
      // Check if the remote is FF.
      const isFirefox = getRemoteFirefoxVersion(arguments[0]);

      // Get the maximum message size the local peer is capable of sending
      const canSendMMS = getCanSendMaxMessageSize(isFirefox);

      // Get the maximum message size of the remote peer.
      const remoteMMS = getMaxMessageSize(arguments[0], isFirefox);

      // Determine final maximum message size
      let maxMessageSize;
      if (canSendMMS === 0 && remoteMMS === 0) {
        maxMessageSize = Number.POSITIVE_INFINITY;
      } else if (canSendMMS === 0 || remoteMMS === 0) {
        maxMessageSize = Math.max(canSendMMS, remoteMMS);
      } else {
        maxMessageSize = Math.min(canSendMMS, remoteMMS);
      }

      // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
      // attribute.
      const sctp = {};
      Object.defineProperty(sctp, 'maxMessageSize', {
        get() {
          return maxMessageSize;
        }
      });
      this._sctp = sctp;
    }

    return origSetRemoteDescription.apply(this, arguments);
  };
}

export function shimSendThrowTypeError(window) {
  if (!(window.RTCPeerConnection &&
      'createDataChannel' in window.RTCPeerConnection.prototype)) {
    return;
  }

  // Note: Although Firefox >= 57 has a native implementation, the maximum
  //       message size can be reset for all data channels at a later stage.
  //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831

  function wrapDcSend(dc, pc) {
    const origDataChannelSend = dc.send;
    dc.send = function() {
      const data = arguments[0];
      const length = data.length || data.size || data.byteLength;
      if (dc.readyState === 'open' &&
          pc.sctp && length > pc.sctp.maxMessageSize) {
        throw new TypeError('Message too large (can send a maximum of ' +
          pc.sctp.maxMessageSize + ' bytes)');
      }
      return origDataChannelSend.apply(dc, arguments);
    };
  }
  const origCreateDataChannel =
    window.RTCPeerConnection.prototype.createDataChannel;
  window.RTCPeerConnection.prototype.createDataChannel = function() {
    const dataChannel = origCreateDataChannel.apply(this, arguments);
    wrapDcSend(dataChannel, this);
    return dataChannel;
  };
  utils.wrapPeerConnectionEvent(window, 'datachannel', e => {
    wrapDcSend(e.channel, e.target);
    return e;
  });
}

/* shims RTCConnectionState by pretending it is the same as iceConnectionState.
 * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
 * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect
 * since DTLS failures would be hidden. See
 * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
 * for the Firefox tracking bug.
 */
export function shimConnectionState(window) {
  if (!window.RTCPeerConnection ||
      'connectionState' in window.RTCPeerConnection.prototype) {
    return;
  }
  const proto = window.RTCPeerConnection.prototype;
  Object.defineProperty(proto, 'connectionState', {
    get() {
      return {
        completed: 'connected',
        checking: 'connecting'
      }[this.iceConnectionState] || this.iceConnectionState;
    },
    enumerable: true,
    configurable: true
  });
  Object.defineProperty(proto, 'onconnectionstatechange', {
    get() {
      return this._onconnectionstatechange || null;
    },
    set(cb) {
      if (this._onconnectionstatechange) {
        this.removeEventListener('connectionstatechange',
            this._onconnectionstatechange);
        delete this._onconnectionstatechange;
      }
      if (cb) {
        this.addEventListener('connectionstatechange',
            this._onconnectionstatechange = cb);
      }
    },
    enumerable: true,
    configurable: true
  });

  ['setLocalDescription', 'setRemoteDescription'].forEach((method) => {
    const origMethod = proto[method];
    proto[method] = function() {
      if (!this._connectionstatechangepoly) {
        this._connectionstatechangepoly = e => {
          const pc = e.target;
          if (pc._lastConnectionState !== pc.connectionState) {
            pc._lastConnectionState = pc.connectionState;
            const newEvent = new Event('connectionstatechange', e);
            pc.dispatchEvent(newEvent);
          }
          return e;
        };
        this.addEventListener('iceconnectionstatechange',
          this._connectionstatechangepoly);
      }
      return origMethod.apply(this, arguments);
    };
  });
}

export function removeAllowExtmapMixed(window) {
  /* remove a=extmap-allow-mixed for Chrome < M71 */
  if (!window.RTCPeerConnection) {
    return;
  }
  const browserDetails = utils.detectBrowser(window);
  if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) {
    return;
  }
  const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription;
  window.RTCPeerConnection.prototype.setRemoteDescription = function(desc) {
    if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) {
      desc.sdp = desc.sdp.split('\n').filter((line) => {
        return line.trim() !== 'a=extmap-allow-mixed';
      }).join('\n');
    }
    return nativeSRD.apply(this, arguments);
  };
}
<|endoftext|>


<commit_before>/*global Logger, I18n, Rules*/
// This creates a "safe" namespace for all libs
var Libs = {
  _libs: {},
  add: function(libraryName, librayTopLevelFunction, forceAdd) {
    // Check if the method is already defined
    forceAdd = forceAdd || false;
    if((this._libs[libraryName] || this.hasOwnProperty(libraryName)) && !forceAdd) {
      Logger.info("[libs.js] Can not add library named '" + libraryName + "' because it already exists as a function().");
      return;
    }
    this[libraryName] = librayTopLevelFunction;
    Logger.info("[libs.js] Added library as Libs." + libraryName);
  },
  import: function() {
    Rules.all().then(function (rules) {
      rules.forEach(function (rule) {
        if (typeof rule.export !== "undefined" && typeof rule.lib === "function") {
          Libs.add(rule.export, rule.lib, true);
        }
      });
    });
  }
};

var valueFunctionHelper = {
  click: function($domNode) {
    $domNode.click();
  }
};
Libs.add("h", valueFunctionHelper);

// Add vendored chance.js
if(typeof window.Chance === "function") {
  Libs.add("chance", new window.Chance());
  window.chance = null;
}

// Add vendored moment.js and set locale
if(typeof window.moment === "function") {
  Libs.add("moment", window.moment);
  Libs.moment.lang(I18n.userLocale());
  window.moment = null;
}

// Import all saved libs
Libs.import();
<commit_msg>add a comment<commit_after>/*global Logger, I18n, Rules*/
// This creates a "safe" namespace for all libs
var Libs = {
  _libs: {},
  add: function(libraryName, librayTopLevelFunction, forceAdd) {
    // Check if the method is already defined
    forceAdd = forceAdd || false;
    if((this._libs[libraryName] || this.hasOwnProperty(libraryName)) && !forceAdd) {
      Logger.info("[libs.js] Can not add library named '" + libraryName + "' because it already exists as a function().");
      return;
    }
    this[libraryName] = librayTopLevelFunction;
    Logger.info("[libs.js] Added library as Libs." + libraryName);
  },
  import: function() {
    Rules.all().then(function (rules) {
      rules.forEach(function (rule) {
        if (typeof rule.export !== "undefined" && typeof rule.lib === "function") {
          Libs.add(rule.export, rule.lib, true);
        }
      });
    });
  }
};

// helper for use in value functions
var valueFunctionHelper = {
  click: function($domNode) {
    $domNode.click();
  }
};
Libs.add("h", valueFunctionHelper);

// Add vendored chance.js
if(typeof window.Chance === "function") {
  Libs.add("chance", new window.Chance());
  window.chance = null;
}

// Add vendored moment.js and set locale
if(typeof window.moment === "function") {
  Libs.add("moment", window.moment);
  Libs.moment.lang(I18n.userLocale());
  window.moment = null;
}

// Import all saved libs
Libs.import();
<|endoftext|>