<commit_before>/**
 * AngularJS <-> Collection+JSON middleware
 */

/**
 * Collection Constructor
 * @param collection The Object or JSON string to construct this Collection from.
 * @throws {ValueError} If href or version properties are not present in collection.
 */
function Collection (collection) {

    var acceptable, acceptable_rules;

    if (!this instanceof Collection) {
        return new Collection(collection);
    }

    this.collection = {};

    // Parse from JSON, if necessary.
    if (typeof collection === 'string') {
        collection = JSON.parse(collection);
    }

    // Unwrap collection contents, if necessary.
    if (collection.hasOwnProperty('collection')) {
        collection = collection.collection;
    }

    this.collection = Collection.prototype.parse(collection);

}

/**
 * Collection properties with type requirements
 */
Collection.prototype.property_rules = {
    error: {constructor: CollectionError},
    href: {type: 'string', required: true},
    items: {constructor: Array, contents: {constructor: CollectionItem}},
    links: {constructor: Array, contents: {constructor: CollectionLink}},
    queries: {constructor: Array, contents: {constructor: CollectionQuery}},
    template: {constructor: CollectionTemplate},
    version: {type: 'string', required: true},
};

/**
 * Collection string method
 */
Collection.prototype.toString = function () {
    return JSON.stringify(this);
}

/**
 * Property validator and parser
 * @param {Object} object The object to parse.
 * @return {Object} The parsed form of the object
 */
Collection.prototype.parse = function (object) {
    var property,
        rule,
        collection = object;

    /**
     * Iterate through an array, supplying parseables into a constructor,
     * passing instances of constructor into an output array.
     * @param {Array} array The array to process
     * @param {function} parseable The constructor for an acceptably parseable object
     * @param {function} constructor The constructor to pass parseable into, if necessary.
     * @return {Array}
     */
    function process_array (array, parseable, constructor) {
        var i = 0,
            output = [],
            length,
            item;

        for (i; i < length; i++) {
            item = array[i];
            if (item instanceof parseable) {
                output.push(constructor(item));
            } else if (item instanceof constructor) {
                output.push(item);
            }
        }

        return output;
    }

    for (prop in this.property_rules) {
        property = object[prop];
        rule = this.property_rules[prop];

        for (r in rule) {
            if (r === 'required' && rule[r]) {
                if (!object.hasOwnProperty(prop)) {
                    throw ValueError(prop + ' is a required property.');
                }
            }

            if (r === 'constructor') {
                if (rule.constructor === Array) {
                    property = process_array(property, Object, rule.contents.constructor);
                } else {
                    if (!property instanceof rule.constructor) {
                        property = rule.constructor(property);
                    }
                }
            }

            if (r === 'type') {
                if (typeof property !== rule.type) {
                    throw TypeError(prop + ' must be of type ' + rule.type);
                }
            }
        }

        collection[prop] = property;
    }

    return collection;
}

/**
 * Hook collection data constructors to use some Collection functions
 * @param {function} constructor The constructor whose prototype should receive these functions
 */
Collection.hook = function (constructor) {
    constructor.prototype.toString = Collection.prototype.toString;
    constructor.prototype.parse = Collection.prototype.parse;
};

/**
 * CollectionData Constructor
 * @param {Object} opts The properties of this field
 */
function CollectionData (opts) {

    if (!this instanceof CollectionData) {
        return new CollectionData(opts);
    }

    opts = CollectionData.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

/**
 * CollectionData property rules
 */
CollectionData.prototype.property_rules = {
    'name': {type: 'string', required: true},
    'prompt': {type: 'string'}
};

Collection.hook(CollectionData);

/**
 * CollectionError Constructor
 */
function CollectionError (opts) {

    if (!this instanceof CollectionError) {
        return new CollectionError(opts);
    }

    opts = CollectionError.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

/**
 * CollectionError property rules
 */
CollectionError.prototype.property_rules = {
    title: {type: 'string'},
    code: {type: 'string'},
    message: {type: 'string'}
};

Collection.hook(CollectionError);

/**
 * CollectionItem Constructor
 */
function CollectionItem (opts) {
    if (!this instanceof CollectionItem) {
        return new CollectionItem(opts);
    }

    opts = CollectionItem.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

/**
 * CollectionItem property rules
 */
CollectionItem.prototype.property_rules = {
    href: Collection.prototype.property_rules.href,
    data: {constructor: Array, contents: {constructor: CollectionData}},
    links: Colleciton.prototype.property_rules.links
};

Collection.hook(CollectionItem);

/**
 * CollectionLink Constructor
 */
function CollectionLink (opts) {
    if (!this instanceof CollectionLink) {
        return new CollectionLink(opts);
    }

    opts = CollectionLink.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

CollectionLink.prototype.property_rules = {
    href: Collection.prototype.property_rules.href,
    rel: {type: 'string', required: true},
    prompt: {type: 'string'},
    name: {type: 'string'},
    render: {type: 'string'}
}

Collection.hook(CollectionLink);

/**
 * CollectionQuery Constructor
 */
function CollectionQuery (opts) {
    if (!this instanceof CollectionQuery) {
        return new CollectionQuery(opts);
    }

    opts = CollectionQuery.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

CollectionQuery.prototype.property_rules = CollectionLink.prototype.property_rules;
delete CollectionQuery.prototype.property_rules.render;
CollectionQuery.prototype.property_rules.data = CollectionItem.prototype.property_rules.data

Collection.hook(CollectionQuery);

/**
 * CollectionTemplate Constructor
 */
function CollectionTemplate (opts) {
    if (!this instanceof CollectionTemplate) {
        return new CollectionTemplate(opts);
    }

    opts = CollectionTemplate.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

CollectionTemplate.prototype.property_rules = {
    data: CollectionItem.prototype.property_rules.data
}

Collection.hook(CollectionTemplate);<commit_msg>* some small fixes<commit_after>/**
 * AngularJS <-> Collection+JSON middleware
 */

/**
 * Collection Constructor
 * @param collection The Object or JSON string to construct this Collection from.
 * @throws {ValueError} If href or version properties are not present in collection.
 */
function Collection (collection) {

    var acceptable, acceptable_rules;

    if (!this instanceof Collection) {
        return new Collection(collection);
    }

    this.collection = {};

    // Parse from JSON, if necessary.
    if (typeof collection === 'string') {
        collection = JSON.parse(collection);
    }

    // Unwrap collection contents, if necessary.
    if (collection.hasOwnProperty('collection')) {
        collection = collection.collection;
    }

    this.collection = Collection.prototype.parse(collection);

}

/**
 * Collection properties with type requirements
 */
Collection.prototype.property_rules = {
    error: {constructor: CollectionError},
    href: {type: 'string', required: true},
    items: {constructor: Array, contents: {constructor: CollectionItem}},
    links: {constructor: Array, contents: {constructor: CollectionLink}},
    queries: {constructor: Array, contents: {constructor: CollectionQuery}},
    template: {constructor: CollectionTemplate},
    version: {type: 'string', required: true},
};

/**
 * Collection string method
 */
Collection.prototype.toString = function () {
    return JSON.stringify(this);
}

/**
 * Property validator and parser
 * @param {Object} object The object to parse.
 * @return {Object} The parsed form of the object
 */
Collection.prototype.parse = function (object) {
    var property,
        rule,
        collection = object;

    /**
     * Iterate through an array, supplying parseables into a constructor,
     * passing instances of constructor into an output array.
     * @param {Array} array The array to process
     * @param {function} parseable The constructor for an acceptably parseable object
     * @param {function} constructor The constructor to pass parseable into, if necessary.
     * @return {Array}
     */
    function process_array (array, parseable, constructor) {
        var i = 0,
            output = [],
            length,
            item;

        for (i; i < length; i++) {
            item = array[i];
            if (item instanceof parseable) {
                output.push(constructor(item));
            } else if (item instanceof constructor) {
                output.push(item);
            }
        }

        if (output.length > 0) {
            return output;
        }
    }

    for (prop in this.property_rules) {
        property = object[prop];
        rule = this.property_rules[prop];

        for (r in rule) {
            if (r === 'required' && rule[r]) {
                if (!object.hasOwnProperty(prop)) {
                    throw ValueError(prop + ' is a required property.');
                }
            }

            if (r === 'constructor') {
                if (rule.constructor === Array) {
                    property = process_array(property, Object, rule.contents.constructor);
                } else {
                    if (!property instanceof rule.constructor) {
                        property = rule.constructor(property);
                    }
                }
            }

            if (r === 'type') {
                if (typeof property !== rule.type) {
                    throw TypeError(prop + ' must be of type ' + rule.type);
                }
            }
        }

        if (property !== undefined) {
            collection[prop] = property;
        }

    }

    return collection;
}

/**
 * Hook collection data constructors to use some Collection functions
 * @param {function} constructor The constructor whose prototype should receive these functions
 */
Collection.hook = function (constructor) {
    constructor.prototype.toString = Collection.prototype.toString;
    constructor.prototype.parse = Collection.prototype.parse;
};

/**
 * CollectionData Constructor
 * @param {Object} opts The properties of this field
 */
function CollectionData (opts) {

    if (!this instanceof CollectionData) {
        return new CollectionData(opts);
    }

    opts = CollectionData.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

/**
 * CollectionData property rules
 */
CollectionData.prototype.property_rules = {
    'name': {type: 'string', required: true},
    'prompt': {type: 'string'}
};

Collection.hook(CollectionData);

/**
 * CollectionError Constructor
 */
function CollectionError (opts) {

    if (!this instanceof CollectionError) {
        return new CollectionError(opts);
    }

    opts = CollectionError.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

/**
 * CollectionError property rules
 */
CollectionError.prototype.property_rules = {
    title: {type: 'string'},
    code: {type: 'string'},
    message: {type: 'string'}
};

Collection.hook(CollectionError);

/**
 * CollectionItem Constructor
 */
function CollectionItem (opts) {
    if (!this instanceof CollectionItem) {
        return new CollectionItem(opts);
    }

    opts = CollectionItem.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

/**
 * CollectionItem property rules
 */
CollectionItem.prototype.property_rules = {
    href: Collection.prototype.property_rules.href,
    data: {constructor: Array, contents: {constructor: CollectionData}},
    links: Collection.prototype.property_rules.links
};

Collection.hook(CollectionItem);

/**
 * CollectionLink Constructor
 */
function CollectionLink (opts) {
    if (!this instanceof CollectionLink) {
        return new CollectionLink(opts);
    }

    opts = CollectionLink.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

CollectionLink.prototype.property_rules = {
    href: Collection.prototype.property_rules.href,
    rel: {type: 'string', required: true},
    prompt: {type: 'string'},
    name: {type: 'string'},
    render: {type: 'string'}
}

Collection.hook(CollectionLink);

/**
 * CollectionQuery Constructor
 */
function CollectionQuery (opts) {
    if (!this instanceof CollectionQuery) {
        return new CollectionQuery(opts);
    }

    opts = CollectionQuery.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

CollectionQuery.prototype.property_rules = CollectionLink.prototype.property_rules;
delete CollectionQuery.prototype.property_rules.render;
CollectionQuery.prototype.property_rules.data = CollectionItem.prototype.property_rules.data

Collection.hook(CollectionQuery);

/**
 * CollectionTemplate Constructor
 */
function CollectionTemplate (opts) {
    if (!this instanceof CollectionTemplate) {
        return new CollectionTemplate(opts);
    }

    opts = CollectionTemplate.prototype.parse(opts);

    for (opt in opts) {
        this[opt] = opts[opt];
    }
}

CollectionTemplate.prototype.property_rules = {
    data: CollectionItem.prototype.property_rules.data
}

Collection.hook(CollectionTemplate);<|endoftext|>


<commit_before>/* global __filename */

import { getLogger } from 'jitsi-meet-logger';

import MediaDirection from '../../service/RTC/MediaDirection';
import * as MediaType from '../../service/RTC/MediaType';

import { SdpTransformWrap } from './SdpTransformUtil';

const logger = getLogger(__filename);

/**
 * Fakes local SDP exposed to {@link JingleSessionPC} through the local
 * description getter. Modifies the SDP, so that it will contain muted local
 * video tracks description, even though their underlying {MediaStreamTrack}s
 * are no longer in the WebRTC peerconnection. That prevents from SSRC updates
 * being sent to Jicofo/remote peer and prevents sRD/sLD cycle on the remote
 * side.
 */
export default class LocalSdpMunger {

    /**
     * Creates new <tt>LocalSdpMunger</tt> instance.
     *
     * @param {TraceablePeerConnection} tpc
     * @param {string} localEndpointId - The endpoint id of the local user.
     */
    constructor(tpc, localEndpointId) {
        this.tpc = tpc;
        this.localEndpointId = localEndpointId;
    }

    /**
     * Makes sure that muted local video tracks associated with the parent
     * {@link TraceablePeerConnection} are described in the local SDP. It's done
     * in order to prevent from sending 'source-remove'/'source-add' Jingle
     * notifications when local video track is muted (<tt>MediaStream</tt> is
     * removed from the peerconnection).
     *
     * NOTE 1 video track is assumed
     *
     * @param {SdpTransformWrap} transformer the transformer instance which will
     * be used to process the SDP.
     * @return {boolean} <tt>true</tt> if there were any modifications to
     * the SDP wrapped by <tt>transformer</tt>.
     * @private
     */
    _addMutedLocalVideoTracksToSDP(transformer) {
        // Go over each video tracks and check if the SDP has to be changed
        const localVideos = this.tpc.getLocalTracks(MediaType.VIDEO);

        if (!localVideos.length) {
            return false;
        } else if (localVideos.length !== 1) {
            logger.error(
                `${this.tpc} there is more than 1 video track ! `
                    + 'Strange things may happen !', localVideos);
        }

        const videoMLine = transformer.selectMedia('video');

        if (!videoMLine) {
            logger.debug(
                `${this.tpc} unable to hack local video track SDP`
                    + '- no "video" media');

            return false;
        }

        let modified = false;

        for (const videoTrack of localVideos) {
            const muted = videoTrack.isMuted();
            const mediaStream = videoTrack.getOriginalStream();

            // During the mute/unmute operation there are periods of time when
            // the track's underlying MediaStream is not added yet to
            // the PeerConnection. The SDP needs to be munged in such case.
            const isInPeerConnection
                = mediaStream && this.tpc.isMediaStreamInPc(mediaStream);
            const shouldFakeSdp = muted || !isInPeerConnection;

            if (!shouldFakeSdp) {
                continue; // eslint-disable-line no-continue
            }

            // Inject removed SSRCs
            const requiredSSRCs
                = this.tpc.isSimulcastOn()
                    ? this.tpc.simulcast.ssrcCache
                    : [ this.tpc.sdpConsistency.cachedPrimarySsrc ];

            if (!requiredSSRCs.length) {
                logger.error(`No SSRCs stored for: ${videoTrack} in ${this.tpc}`);

                continue; // eslint-disable-line no-continue
            }

            modified = true;

            // We need to fake sendrecv.
            // NOTE the SDP produced here goes only to Jicofo and is never set
            // as localDescription. That's why
            // TraceablePeerConnection.mediaTransferActive is ignored here.
            videoMLine.direction = MediaDirection.SENDRECV;

            // Check if the recvonly has MSID
            const primarySSRC = requiredSSRCs[0];

            // FIXME The cname could come from the stream, but may turn out to
            // be too complex. It is fine to come up with any value, as long as
            // we only care about the actual SSRC values when deciding whether
            // or not an update should be sent.
            const primaryCname = `injected-${primarySSRC}`;

            for (const ssrcNum of requiredSSRCs) {
                // Remove old attributes
                videoMLine.removeSSRC(ssrcNum);

                // Inject
                videoMLine.addSSRCAttribute({
                    id: ssrcNum,
                    attribute: 'cname',
                    value: primaryCname
                });
                videoMLine.addSSRCAttribute({
                    id: ssrcNum,
                    attribute: 'msid',
                    value: videoTrack.storedMSID
                });
            }
            if (requiredSSRCs.length > 1) {
                const group = {
                    ssrcs: requiredSSRCs.join(' '),
                    semantics: 'SIM'
                };

                if (!videoMLine.findGroup(group.semantics, group.ssrcs)) {
                    // Inject the group
                    videoMLine.addSSRCGroup(group);
                }
            }

            // Insert RTX
            // FIXME in P2P RTX is used by Chrome regardless of config option
            // status. Because of that 'source-remove'/'source-add'
            // notifications are still sent to remove/add RTX SSRC and FID group
            if (!this.tpc.options.disableRtx) {
                this.tpc.rtxModifier.modifyRtxSsrcs2(videoMLine);
            }
        }

        return modified;
    }

    /**
     * Returns a string that can be set as the MSID attribute for a source.
     *
     * @param {string} mediaType - Media type of the source.
     * @param {string} trackId - Id of the MediaStreamTrack associated with the source.
     * @param {string} streamId - Id of the MediaStream associated with the source.
     * @returns {string|null}
     */
    _generateMsidAttribute(mediaType, trackId, streamId = null) {
        if (!(mediaType && trackId)) {
            logger.warn(`Unable to munge local MSID - track id=${trackId} or media type=${mediaType} is missing`);

            return null;
        }
        const pcId = this.tpc.id;

        // Handle a case on Firefox when the browser doesn't produce a 'a:ssrc' line with the 'msid' attribute or has
        // '-' for the stream id part of the msid line. Jicofo needs an unique identifier to be associated with a ssrc
        // and uses the msid for that.
        if (streamId === '-' || !streamId) {
            return `${this.localEndpointId}-${mediaType}-${pcId} ${trackId}-${pcId}`;
        }

        return `${streamId}-${pcId} ${trackId}-${pcId}`;
    }

    /**
     * Modifies 'cname', 'msid', 'label' and 'mslabel' by appending
     * the id of {@link LocalSdpMunger#tpc} at the end, preceding by a dash
     * sign.
     *
     * @param {MLineWrap} mediaSection - The media part (audio or video) of the
     * session description which will be modified in place.
     * @returns {void}
     * @private
     */
    _transformMediaIdentifiers(mediaSection) {
        const pcId = this.tpc.id;

        for (const ssrcLine of mediaSection.ssrcs) {
            switch (ssrcLine.attribute) {
            case 'cname':
            case 'label':
            case 'mslabel':
                ssrcLine.value = ssrcLine.value && `${ssrcLine.value}-${pcId}`;
                break;
            case 'msid': {
                if (ssrcLine.value) {
                    const streamAndTrackIDs = ssrcLine.value.split(' ');

                    if (streamAndTrackIDs.length === 2) {
                        ssrcLine.value
                            = this._generateMsidAttribute(
                                mediaSection.mLine?.type,
                                streamAndTrackIDs[1],
                                streamAndTrackIDs[0]);
                    } else {
                        logger.warn(`Unable to munge local MSID - weird format detected: ${ssrcLine.value}`);
                    }
                }
                break;
            }
            }
        }

        // Additional transformations related to MSID are applicable to Unified-plan implementation only.
        if (!this.tpc.usesUnifiedPlan()) {
            return;
        }

        // If the msid attribute is missing, then remove the ssrc from the transformed description so that a
        // source-remove is signaled to Jicofo. This happens when the direction of the transceiver (or m-line)
        // is set to 'inactive' or 'recvonly' on Firefox, Chrome (unified) and Safari.
        const mediaDirection = mediaSection.mLine?.direction;

        if (mediaDirection === MediaDirection.RECVONLY || mediaDirection === MediaDirection.INACTIVE) {
            mediaSection.ssrcs = undefined;
            mediaSection.ssrcGroups = undefined;

        // Add the msid attribute if it is missing when the direction is sendrecv/sendonly. Firefox doesn't produce a
        // a=ssrc line with msid attribute for p2p connection.
        } else {
            const msidLine = mediaSection.mLine?.msid;
            const trackId = msidLine && msidLine.split(' ')[1];
            const sources = [ ...new Set(mediaSection.mLine?.ssrcs?.map(s => s.id)) ];

            for (const source of sources) {
                const msidExists = mediaSection.ssrcs
                    .find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');

                if (!msidExists) {
                    const generatedMsid = this._generateMsidAttribute(mediaSection.mLine?.type, trackId);

                    mediaSection.ssrcs.push({
                        id: source,
                        attribute: 'msid',
                        value: generatedMsid
                    });
                }
            }
        }
    }

    /**
     * Maybe modifies local description to fake local video tracks SDP when
     * those are muted.
     *
     * @param {object} desc the WebRTC SDP object instance for the local
     * description.
     * @returns {RTCSessionDescription}
     */
    maybeAddMutedLocalVideoTracksToSDP(desc) {
        if (!desc) {
            throw new Error('No local description passed in.');
        }

        const transformer = new SdpTransformWrap(desc.sdp);

        if (this._addMutedLocalVideoTracksToSDP(transformer)) {
            return new RTCSessionDescription({
                type: desc.type,
                sdp: transformer.toRawSDP()
            });
        }

        return desc;
    }

    /**
     * This transformation will make sure that stream identifiers are unique
     * across all of the local PeerConnections even if the same stream is used
     * by multiple instances at the same time.
     * Each PeerConnection assigns different SSRCs to the same local
     * MediaStream, but the MSID remains the same as it's used to identify
     * the stream by the WebRTC backend. The transformation will append
     * {@link TraceablePeerConnection#id} at the end of each stream's identifier
     * ("cname", "msid", "label" and "mslabel").
     *
     * @param {RTCSessionDescription} sessionDesc - The local session
     * description (this instance remains unchanged).
     * @return {RTCSessionDescription} - Transformed local session description
     * (a modified copy of the one given as the input).
     */
    transformStreamIdentifiers(sessionDesc) {
        // FIXME similar check is probably duplicated in all other transformers
        if (!sessionDesc || !sessionDesc.sdp || !sessionDesc.type) {
            return sessionDesc;
        }

        const transformer = new SdpTransformWrap(sessionDesc.sdp);
        const audioMLine = transformer.selectMedia('audio');

        if (audioMLine) {
            this._transformMediaIdentifiers(audioMLine);
        }

        const videoMLine = transformer.selectMedia('video');

        if (videoMLine) {
            this._transformMediaIdentifiers(videoMLine);
        }

        return new RTCSessionDescription({
            type: sessionDesc.type,
            sdp: transformer.toRawSDP()
        });
    }
}
<commit_msg>fix(LocalSdpMunger): do not fake video sdp when screen sharing<commit_after>/* global __filename */

import { getLogger } from 'jitsi-meet-logger';

import MediaDirection from '../../service/RTC/MediaDirection';
import * as MediaType from '../../service/RTC/MediaType';
import VideoType from '../../service/RTC/VideoType';

import { SdpTransformWrap } from './SdpTransformUtil';

const logger = getLogger(__filename);

/**
 * Fakes local SDP exposed to {@link JingleSessionPC} through the local
 * description getter. Modifies the SDP, so that it will contain muted local
 * video tracks description, even though their underlying {MediaStreamTrack}s
 * are no longer in the WebRTC peerconnection. That prevents from SSRC updates
 * being sent to Jicofo/remote peer and prevents sRD/sLD cycle on the remote
 * side.
 */
export default class LocalSdpMunger {

    /**
     * Creates new <tt>LocalSdpMunger</tt> instance.
     *
     * @param {TraceablePeerConnection} tpc
     * @param {string} localEndpointId - The endpoint id of the local user.
     */
    constructor(tpc, localEndpointId) {
        this.tpc = tpc;
        this.localEndpointId = localEndpointId;
    }

    /**
     * Makes sure that muted local video tracks associated with the parent
     * {@link TraceablePeerConnection} are described in the local SDP. It's done
     * in order to prevent from sending 'source-remove'/'source-add' Jingle
     * notifications when local video track is muted (<tt>MediaStream</tt> is
     * removed from the peerconnection).
     *
     * NOTE 1 video track is assumed
     *
     * @param {SdpTransformWrap} transformer the transformer instance which will
     * be used to process the SDP.
     * @return {boolean} <tt>true</tt> if there were any modifications to
     * the SDP wrapped by <tt>transformer</tt>.
     * @private
     */
    _addMutedLocalVideoTracksToSDP(transformer) {
        // Go over each video tracks and check if the SDP has to be changed
        const localVideos = this.tpc.getLocalTracks(MediaType.VIDEO);

        if (!localVideos.length) {
            return false;
        } else if (localVideos.length !== 1) {
            logger.error(
                `${this.tpc} there is more than 1 video track ! `
                    + 'Strange things may happen !', localVideos);
        }

        const videoMLine = transformer.selectMedia('video');

        if (!videoMLine) {
            logger.debug(
                `${this.tpc} unable to hack local video track SDP`
                    + '- no "video" media');

            return false;
        }

        let modified = false;

        for (const videoTrack of localVideos) {
            const muted = videoTrack.isMuted();
            const mediaStream = videoTrack.getOriginalStream();
            const isCamera = videoTrack.videoType === VideoType.CAMERA;

            // During the mute/unmute operation there are periods of time when
            // the track's underlying MediaStream is not added yet to
            // the PeerConnection. The SDP needs to be munged in such case.
            const isInPeerConnection
                = mediaStream && this.tpc.isMediaStreamInPc(mediaStream);
            const shouldFakeSdp = isCamera && (muted || !isInPeerConnection);

            if (!shouldFakeSdp) {
                continue; // eslint-disable-line no-continue
            }

            // Inject removed SSRCs
            const requiredSSRCs
                = this.tpc.isSimulcastOn()
                    ? this.tpc.simulcast.ssrcCache
                    : [ this.tpc.sdpConsistency.cachedPrimarySsrc ];

            if (!requiredSSRCs.length) {
                logger.error(`No SSRCs stored for: ${videoTrack} in ${this.tpc}`);

                continue; // eslint-disable-line no-continue
            }

            modified = true;

            // We need to fake sendrecv.
            // NOTE the SDP produced here goes only to Jicofo and is never set
            // as localDescription. That's why
            // TraceablePeerConnection.mediaTransferActive is ignored here.
            videoMLine.direction = MediaDirection.SENDRECV;

            // Check if the recvonly has MSID
            const primarySSRC = requiredSSRCs[0];

            // FIXME The cname could come from the stream, but may turn out to
            // be too complex. It is fine to come up with any value, as long as
            // we only care about the actual SSRC values when deciding whether
            // or not an update should be sent.
            const primaryCname = `injected-${primarySSRC}`;

            for (const ssrcNum of requiredSSRCs) {
                // Remove old attributes
                videoMLine.removeSSRC(ssrcNum);

                // Inject
                videoMLine.addSSRCAttribute({
                    id: ssrcNum,
                    attribute: 'cname',
                    value: primaryCname
                });
                videoMLine.addSSRCAttribute({
                    id: ssrcNum,
                    attribute: 'msid',
                    value: videoTrack.storedMSID
                });
            }
            if (requiredSSRCs.length > 1) {
                const group = {
                    ssrcs: requiredSSRCs.join(' '),
                    semantics: 'SIM'
                };

                if (!videoMLine.findGroup(group.semantics, group.ssrcs)) {
                    // Inject the group
                    videoMLine.addSSRCGroup(group);
                }
            }

            // Insert RTX
            // FIXME in P2P RTX is used by Chrome regardless of config option
            // status. Because of that 'source-remove'/'source-add'
            // notifications are still sent to remove/add RTX SSRC and FID group
            if (!this.tpc.options.disableRtx) {
                this.tpc.rtxModifier.modifyRtxSsrcs2(videoMLine);
            }
        }

        return modified;
    }

    /**
     * Returns a string that can be set as the MSID attribute for a source.
     *
     * @param {string} mediaType - Media type of the source.
     * @param {string} trackId - Id of the MediaStreamTrack associated with the source.
     * @param {string} streamId - Id of the MediaStream associated with the source.
     * @returns {string|null}
     */
    _generateMsidAttribute(mediaType, trackId, streamId = null) {
        if (!(mediaType && trackId)) {
            logger.warn(`Unable to munge local MSID - track id=${trackId} or media type=${mediaType} is missing`);

            return null;
        }
        const pcId = this.tpc.id;

        // Handle a case on Firefox when the browser doesn't produce a 'a:ssrc' line with the 'msid' attribute or has
        // '-' for the stream id part of the msid line. Jicofo needs an unique identifier to be associated with a ssrc
        // and uses the msid for that.
        if (streamId === '-' || !streamId) {
            return `${this.localEndpointId}-${mediaType}-${pcId} ${trackId}-${pcId}`;
        }

        return `${streamId}-${pcId} ${trackId}-${pcId}`;
    }

    /**
     * Modifies 'cname', 'msid', 'label' and 'mslabel' by appending
     * the id of {@link LocalSdpMunger#tpc} at the end, preceding by a dash
     * sign.
     *
     * @param {MLineWrap} mediaSection - The media part (audio or video) of the
     * session description which will be modified in place.
     * @returns {void}
     * @private
     */
    _transformMediaIdentifiers(mediaSection) {
        const pcId = this.tpc.id;

        for (const ssrcLine of mediaSection.ssrcs) {
            switch (ssrcLine.attribute) {
            case 'cname':
            case 'label':
            case 'mslabel':
                ssrcLine.value = ssrcLine.value && `${ssrcLine.value}-${pcId}`;
                break;
            case 'msid': {
                if (ssrcLine.value) {
                    const streamAndTrackIDs = ssrcLine.value.split(' ');

                    if (streamAndTrackIDs.length === 2) {
                        ssrcLine.value
                            = this._generateMsidAttribute(
                                mediaSection.mLine?.type,
                                streamAndTrackIDs[1],
                                streamAndTrackIDs[0]);
                    } else {
                        logger.warn(`Unable to munge local MSID - weird format detected: ${ssrcLine.value}`);
                    }
                }
                break;
            }
            }
        }

        // Additional transformations related to MSID are applicable to Unified-plan implementation only.
        if (!this.tpc.usesUnifiedPlan()) {
            return;
        }

        // If the msid attribute is missing, then remove the ssrc from the transformed description so that a
        // source-remove is signaled to Jicofo. This happens when the direction of the transceiver (or m-line)
        // is set to 'inactive' or 'recvonly' on Firefox, Chrome (unified) and Safari.
        const mediaDirection = mediaSection.mLine?.direction;

        if (mediaDirection === MediaDirection.RECVONLY || mediaDirection === MediaDirection.INACTIVE) {
            mediaSection.ssrcs = undefined;
            mediaSection.ssrcGroups = undefined;

        // Add the msid attribute if it is missing when the direction is sendrecv/sendonly. Firefox doesn't produce a
        // a=ssrc line with msid attribute for p2p connection.
        } else {
            const msidLine = mediaSection.mLine?.msid;
            const trackId = msidLine && msidLine.split(' ')[1];
            const sources = [ ...new Set(mediaSection.mLine?.ssrcs?.map(s => s.id)) ];

            for (const source of sources) {
                const msidExists = mediaSection.ssrcs
                    .find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');

                if (!msidExists) {
                    const generatedMsid = this._generateMsidAttribute(mediaSection.mLine?.type, trackId);

                    mediaSection.ssrcs.push({
                        id: source,
                        attribute: 'msid',
                        value: generatedMsid
                    });
                }
            }
        }
    }

    /**
     * Maybe modifies local description to fake local video tracks SDP when
     * those are muted.
     *
     * @param {object} desc the WebRTC SDP object instance for the local
     * description.
     * @returns {RTCSessionDescription}
     */
    maybeAddMutedLocalVideoTracksToSDP(desc) {
        if (!desc) {
            throw new Error('No local description passed in.');
        }

        const transformer = new SdpTransformWrap(desc.sdp);

        if (this._addMutedLocalVideoTracksToSDP(transformer)) {
            return new RTCSessionDescription({
                type: desc.type,
                sdp: transformer.toRawSDP()
            });
        }

        return desc;
    }

    /**
     * This transformation will make sure that stream identifiers are unique
     * across all of the local PeerConnections even if the same stream is used
     * by multiple instances at the same time.
     * Each PeerConnection assigns different SSRCs to the same local
     * MediaStream, but the MSID remains the same as it's used to identify
     * the stream by the WebRTC backend. The transformation will append
     * {@link TraceablePeerConnection#id} at the end of each stream's identifier
     * ("cname", "msid", "label" and "mslabel").
     *
     * @param {RTCSessionDescription} sessionDesc - The local session
     * description (this instance remains unchanged).
     * @return {RTCSessionDescription} - Transformed local session description
     * (a modified copy of the one given as the input).
     */
    transformStreamIdentifiers(sessionDesc) {
        // FIXME similar check is probably duplicated in all other transformers
        if (!sessionDesc || !sessionDesc.sdp || !sessionDesc.type) {
            return sessionDesc;
        }

        const transformer = new SdpTransformWrap(sessionDesc.sdp);
        const audioMLine = transformer.selectMedia('audio');

        if (audioMLine) {
            this._transformMediaIdentifiers(audioMLine);
        }

        const videoMLine = transformer.selectMedia('video');

        if (videoMLine) {
            this._transformMediaIdentifiers(videoMLine);
        }

        return new RTCSessionDescription({
            type: sessionDesc.type,
            sdp: transformer.toRawSDP()
        });
    }
}
<|endoftext|>


<commit_before>/**
 * ghapi.js
 * http://github.com/rhoffmann8/ghtpi.js
 *
 * An extremely low-power wrapper library for (some of) Github API's JSONP functionality.
 * This was written for the sole purpose of facilitating a joke webpage polling GH to check
 * how many new JS framework repositories were created that day. This could on some level be
 * considered ironic given I essentially wrote a framework to do just that (well, sort of).
 *
 * Copyright (c) 2015 Rob Hoffmann <rhoffmann8@gmail.com>
 * Licensed under the MIT license.
 *
 * I may or may not improve this in the future.
 */

var GHApi = ( function( window, document, undefined ) {

    var _path,
        _query,
        _params = {},
        _filters = {},
        _callback = null,
        _funcCount = 0;

    var addParam = function(param, value) {
        return ( _params[param] = value, this );
    };

    var clearParams = function() {
        return ( _params = {}, this );
    }

    // filtering
    var addFilter = function(param, value) {
        return ( _filters[param] = value, this );
    };

    var clearFilters = function() {
        return ( _filters = {}, this );
    };

    // 'q' param
    var query = function(queryText) {
        return ( _params.q = queryText.split(' ').join('+'), this );
    };

    var setCallback = function(cb) {
        return ( _callback = cb, this );
    };

    // set endpoint
    var open = function(path) {
        if (!path) {
            throw new Error('path cannot be empty!');
        }

        return ( _path = path, this );
    };

    // args passed in this call override any set in object members
    var submit = function(path, params, filters, cb) {
        return ( _api(path, params, filters, cb), this );
    };

    var _parseParams = function(params, filters) {
        var q = 'q=' + (params.q || _query),
            queryParams = [],
            tmpParams = [],
            tmpFilters = [];

        for (var prop in params) {
            if (prop == 'q') continue;
            tmpParams.push(prop + '=' + params[prop]);
        }

        for (var prop in filters) {
            tmpFilters.push(prop + ':' + filters[prop]);
        }

        if (tmpFilters.length) {
            q += '+' + tmpFilters.join('+');
        }

        queryParams.push(q);
        if (tmpParams.length) queryParams.push(tmpParams.join('&'));

        // join params and filters
        queryParams = queryParams.join('&').replace(' ', '+');

        return queryParams;
    }

    var _api = function(path, params, filters, cb) {
        var script = document.createElement('script'),
            cb = cb || _callback,
            request = {
                path:     path || _path,
                params: _parseParams(params || _params, filters || _filters)
            };

        if (!cb) {
            throw new Error('callback must be set!');
        }

        // new cb to handle specific request
        window['cors'+_funcCount] = (function(i, req) {
            return function(response) {
                cb.bind(req)(response.meta, response.data);

                delete window['cors'+i]; // cleanup
            };
        })(_funcCount, request);

        script.src = '//api.github.com' + request.path + '?callback=cors'+(_funcCount++)+(request.params.length ? '&' + request.params : '');
        document.getElementsByTagName('head')[0].appendChild(script);
    };

    return {
        addParam: addParam,
        addFilter: addFilter,
        clearFilters, clearFilters,
        open: open,
        query: query,
        setCallback: setCallback,
        submit: submit
    };

})( this, this.document );
<commit_msg>Update ghapi.js<commit_after>/**
 * ghapi.js
 * http://github.com/rhoffmann8/ghapi.js
 *
 * An extremely low-power wrapper library for (some of) Github API's JSONP functionality.
 * This was written for the sole purpose of facilitating a joke webpage polling GH to check
 * how many new JS framework repositories were created that day. This could on some level be
 * considered ironic given I essentially wrote a framework to do just that (well, sort of).
 *
 * Copyright (c) 2015 Rob Hoffmann <rhoffmann8@gmail.com>
 * Licensed under the MIT license.
 *
 * I may or may not improve this in the future.
 */

var GHApi = ( function( window, document, undefined ) {

    var _path,
        _query,
        _params = {},
        _filters = {},
        _callback = null,
        _funcCount = 0;

    var addParam = function(param, value) {
        return ( _params[param] = value, this );
    };

    var clearParams = function() {
        return ( _params = {}, this );
    }

    // filtering
    var addFilter = function(param, value) {
        return ( _filters[param] = value, this );
    };

    var clearFilters = function() {
        return ( _filters = {}, this );
    };

    // 'q' param
    var query = function(queryText) {
        return ( _params.q = queryText.split(' ').join('+'), this );
    };

    var setCallback = function(cb) {
        return ( _callback = cb, this );
    };

    // set endpoint
    var open = function(path) {
        if (!path) {
            throw new Error('path cannot be empty!');
        }

        return ( _path = path, this );
    };

    // args passed in this call override any set in object members
    var submit = function(path, params, filters, cb) {
        return ( _api(path, params, filters, cb), this );
    };

    var _parseParams = function(params, filters) {
        var q = 'q=' + (params.q || _query),
            queryParams = [],
            tmpParams = [],
            tmpFilters = [];

        for (var prop in params) {
            if (prop == 'q') continue;
            tmpParams.push(prop + '=' + params[prop]);
        }

        for (var prop in filters) {
            tmpFilters.push(prop + ':' + filters[prop]);
        }

        if (tmpFilters.length) {
            q += '+' + tmpFilters.join('+');
        }

        queryParams.push(q);
        if (tmpParams.length) queryParams.push(tmpParams.join('&'));

        // join params and filters
        queryParams = queryParams.join('&').replace(' ', '+');

        return queryParams;
    }

    var _api = function(path, params, filters, cb) {
        var script = document.createElement('script'),
            cb = cb || _callback,
            request = {
                path:     path || _path,
                params: _parseParams(params || _params, filters || _filters)
            };

        if (!cb) {
            throw new Error('callback must be set!');
        }

        // new cb to handle specific request
        window['cors'+_funcCount] = (function(i, req) {
            return function(response) {
                cb.bind(req)(response.meta, response.data);

                delete window['cors'+i]; // cleanup
            };
        })(_funcCount, request);

        script.src = '//api.github.com' + request.path + '?callback=cors'+(_funcCount++)+(request.params.length ? '&' + request.params : '');
        document.getElementsByTagName('head')[0].appendChild(script);
    };

    return {
        addParam: addParam,
        addFilter: addFilter,
        clearFilters, clearFilters,
        open: open,
        query: query,
        setCallback: setCallback,
        submit: submit
    };

})( this, this.document );
<|endoftext|>


<commit_before>'use strict';

var rewire = require('rewire');

var sh = rewire('../bin/shampoo');
var Shampoo = sh.Shampoo;

var sendSpy = jasmine.createSpy('WS.Send');
var closeSpy = jasmine.createSpy('WS.Close');
var wsSpy = jasmine.createSpy('WebSocket').and.callFake(
    function() {
        this.onopen = undefined;
        this.onmessage = undefined;
        this.onerror = undefined;
        this.onclose = undefined;
        this.send = sendSpy;
        this.close = closeSpy;

        return this;
    });
sh.__set__('WebSocket', wsSpy);

describe('Shampoo', () => {
    beforeEach(() => {
        this.s = new Shampoo('foo');
    });
    afterEach(() => {
        this.s.close();
        this.s = undefined;
    });

    it('should instantiate right', () => {
        expect(wsSpy).toHaveBeenCalledWith('foo', 'shampoo');
    });

    it('should open correctly', () => {
        this.s.socket.onopen();
        expect(this.s.ready).toBe(true);
    });

    it('should allow me to call a method', () => {
        spyOn(this.s, 'sendMessage');
        this.s.call('foo', {});

        expect(this.s.sendMessage).toHaveBeenCalled();
    });

    it('should keep its promises', (done) => {
        this.s.call('foo', {})
            .then(done, () => {});

        this.s.socket.onmessage({data: {
            status: 200,
            message: 'ok',
            response_data: {},
            request_index_number: 1,
        }});
    });

    it('should keep its promises even if something goes wrong', (done) => {
        this.s.call('foo', {})
            .then(() => {}, done);

        this.s.socket.onmessage({data: {
            status: 501,
            message: 'Server Error',
            response_data: {},
            request_index_number: 1,
        }});
    });
});
<commit_msg>Add multiple requests test<commit_after>'use strict';

var rewire = require('rewire');

var sh = rewire('../bin/shampoo');
var Shampoo = sh.Shampoo;

var sendSpy = jasmine.createSpy('WS.Send');
var closeSpy = jasmine.createSpy('WS.Close');
var wsSpy = jasmine.createSpy('WebSocket').and.callFake(
    function() {
        this.onopen = undefined;
        this.onmessage = undefined;
        this.onerror = undefined;
        this.onclose = undefined;
        this.send = sendSpy;
        this.close = closeSpy;

        return this;
    });
sh.__set__('WebSocket', wsSpy);

describe('Shampoo', () => {
    beforeEach(() => {
        this.s = new Shampoo('foo');
    });
    afterEach(() => {
        this.s.close();
        this.s = undefined;
    });

    it('should instantiate right', () => {
        expect(wsSpy).toHaveBeenCalledWith('foo', 'shampoo');
    });

    it('should open correctly', () => {
        this.s.socket.onopen();
        expect(this.s.ready).toBe(true);
    });

    it('should allow me to call a method', () => {
        spyOn(this.s, 'sendMessage');
        this.s.call('foo', {});

        expect(this.s.sendMessage).toHaveBeenCalled();
    });

    it('should keep its promises', (done) => {
        this.s.call('foo', {})
            .then(done, () => {});

        this.s.socket.onmessage({data: {
            status: 200,
            message: 'ok',
            response_data: {},
            request_index_number: 1,
        }});
    });

    it('should keep its promises even if something goes wrong', (done) => {
        this.s.call('foo', {})
            .then(() => {}, done);

        this.s.socket.onmessage({data: {
            status: 501,
            message: 'Server Error',
            response_data: {},
            request_index_number: 1,
        }});
    });

    it('should be able to keep track of multiple requests', (done) => {
        this.s.call('foo', {})
            .then(() => {});
        this.s.call('bar', {})
            .then(done, () => {});

        this.s.socket.onmessage({data: {
            status: 200,
            message: 'ok',
            response_data: {},
            request_index_number: 1,
        }});

        this.s.socket.onmessage({data: {
            status: 200,
            message: 'ok',
            response_data: {},
            request_index_number: 2,
        }});
    });
});
<|endoftext|>


<commit_before>import checkPermissions from 'xo-acl-resolver'
import filter from 'lodash/filter'
import find from 'lodash/find'
import forEach from 'lodash/forEach'
import groupBy from 'lodash/groupBy'
import isArray from 'lodash/isArray'
import isArrayLike from 'lodash/isArrayLike'
import isFunction from 'lodash/isFunction'
import orderBy from 'lodash/orderBy'
import pickBy from 'lodash/pickBy'
import size from 'lodash/size'
import slice from 'lodash/slice'
import { createSelector as create } from 'reselect'

import shallowEqual from './shallow-equal'
import { EMPTY_ARRAY, EMPTY_OBJECT, invoke } from './utils'

// ===================================================================

export {
  // That's usually the name we want to import.
  createSelector,

  // But selectors.create is nice too :)
  createSelector as create
} from 'reselect'

// -------------------------------------------------------------------

// Wraps a function which returns a collection to returns the previous
// result if the collection has not really changed (ie still has the
// same items).
//
// Use case: in connect, to avoid rerendering a component where the
// objects are still the same.
const _createCollectionWrapper = selector => {
  let cache

  return (...args) => {
    const value = selector(...args)
    if (!shallowEqual(value, cache)) {
      cache = value
    }
    return cache
  }
}
export { _createCollectionWrapper as createCollectionWrapper }

const _SELECTOR_PLACEHOLDER = Symbol('selector placeholder')

// Experimental!
//
// Similar to reselect's createSelector() but inputs can be either
// selectors or plain values.
//
// To pass a function as a plain value, simply wrap it with an array.
const _create2 = (...inputs) => {
  const resultFn = inputs.pop()

  if (inputs.length === 1 && isArray(inputs[0])) {
    inputs = inputs[0]
  }

  const n = inputs.length

  const inputSelectors = []
  for (let i = 0; i < n; ++i) {
    const input = inputs[i]

    if (isFunction(input)) {
      inputSelectors.push(input)
      inputs[i] = _SELECTOR_PLACEHOLDER
    } else if (isArray(input) && input.length === 1) {
      inputs[i] = input[0]
    }
  }

  if (!inputSelectors.length) {
    throw new Error('no input selectors')
  }

  return create(inputSelectors, function () {
    const args = new Array(n)
    for (let i = 0, j = 0; i < n; ++i) {
      const input = inputs[i]
      args[i] = input === _SELECTOR_PLACEHOLDER
        ? arguments[j++]
        : input
    }

    return resultFn.apply(this, args)
  })
}

// ===================================================================
// Generic selector creators.

export const createCounter = (collection, predicate) =>
  _create2(
    collection,
    predicate,
    (collection, predicate) => {
      if (!predicate) {
        return size(collection)
      }

      let count = 0
      forEach(collection, item => {
        if (predicate(item)) {
          ++count
        }
      })
      return count
    }
  )

// Creates an object selector from an object selector and a properties
// selector.
//
// Should only be used with a reasonable number of properties.
export const createPicker = (object, props) =>
  _createCollectionWrapper(
    _create2(
      object, props,
      (object, props) => {
        const values = {}
        forEach(props, prop => {
          const value = object[prop]
          if (value) {
            values[prop] = value
          }
        })
        return values
      }
    )
  )

// Special cases:
// - predicate == null → no filtering
// - predicate === false → everything is filtered out
export const createFilter = (collection, predicate) =>
  _createCollectionWrapper(
    _create2(
      collection,
      predicate,
      (collection, predicate) => predicate === false
        ? (isArrayLike(collection) ? EMPTY_ARRAY : EMPTY_OBJECT)
        : predicate
          ? (isArrayLike(collection) ? filter : pickBy)(collection, predicate)
          : collection
    )
  )

export const createFinder = (collection, predicate) =>
  _create2(
    collection,
    predicate,
    find
  )

export const createGroupBy = (collection, getter) =>
  _create2(
    collection,
    getter,
    groupBy
  )

export const createPager = (array, page, n = 25) => _createCollectionWrapper(
  _create2(
    array,
    page,
    n,
    (array, page, n) => {
      const start = (page - 1) * n
      return slice(array, start, start + n)
    }
  )
)

export const createSort = (
  collection,
  getter = 'name_label',
  order = 'asc'
) => _create2(collection, getter, order, orderBy)

export const createTop = (collection, iteratee, n) =>
  _createCollectionWrapper(
    _create2(
      collection,
      iteratee,
      n,
      (objects, iteratee, n) => {
        let results = orderBy(objects, iteratee, 'desc')
        if (n < results.length) {
          results.length = n
        }
        return results
      }
    )
  )

// ===================================================================
// Root-ish selectors (no dependencies).

export const areObjectsFetched = state => state.objects.fetched

const _getId = (state, { routeParams, id }) => routeParams
  ? routeParams.id
  : id

export const getLang = state => state.lang

export const getUser = state => state.user

const _getPermissionsPredicate = invoke(() => {
  const getPredicate = create(
    state => state.permissions,
    state => state.objects,
    (permissions, objects) => {
      objects = objects.all
      const getObject = id => (objects[id] || EMPTY_OBJECT)

      return id => checkPermissions(permissions, getObject, id.id || id, 'view')
    }
  )

  return state => {
    const user = getUser(state)
    if (!user) {
      return false
    }

    if (user.permission === 'admin') {
      return // No predicate means no filtering.
    }

    return getPredicate(state)
  }
})

// ===================================================================
// Common selector creators.

// Creates an object selector from an id selector.
export const createGetObject = (idSelector = _getId) =>
  (state, props) => {
    const object = state.objects.all[idSelector(state, props)]
    if (!object) {
      return
    }

    const predicate = _getPermissionsPredicate(state)
    if (!predicate) {
      if (predicate == null) {
        return object // no filtering
      }

      // predicate is false.
      return
    }

    if (predicate(object)) {
      return object
    }
  }

// Specialized createSort() configured for a given type.
export const createSortForType = invoke(() => {
  const optionsByType = {
    message: [
      [ message => message.time ],
      'desc'
    ],
    PIF: [
      [ pif => pif.device ]
    ],
    pool_patch: [
      [ patch => patch.name ]
    ],
    VBD: [
      [ vbd => vbd.position ]
    ],
    'VDI-snapshot': [
      [ snapshot => snapshot.snapshot_time ],
      'desc'
    ],
    'VM-snapshot': [
      [ snapshot => snapshot.snapshot_time ],
      'desc'
    ]
  }
  const defaults = [
    [ [ object => object.$pool, object => object.name_label ] ]
  ]
  const getOptions = type => optionsByType[type] || defaults

  return (type, collection) => createSort(collection, ...getOptions(type))
})

// Add utility methods to a collection selector.
const _extendCollectionSelector = (selector, objectsType) => {
  // Terminal methods.
  const _addCount = selector => {
    selector.count = predicate => createCounter(selector, predicate)
    return selector
  }
  _addCount(selector)
  const _addGroupBy = selector => {
    selector.groupBy = getter => createGroupBy(selector, getter)
    return selector
  }
  _addGroupBy(selector)
  selector.find = predicate => createFinder(selector, predicate)

  // groupBy can be chained.
  const _addSort = selector => {
    // TODO: maybe memoize when no idsSelector.
    selector.sort = () => _addGroupBy(createSortForType(objectsType, selector))
    return selector
  }
  _addSort(selector)

  // groupBy and sort can be chained.
  selector.pick = idsSelector => _addGroupBy(_addSort(
    createPicker(selector, idsSelector)
  ))

  // count, groupBy and sort can be chained.
  selector.filter = predicate => _addCount(_addGroupBy(_addSort(
    createFilter(selector, predicate)
  )))

  return selector
}

// Creates a collection selector which returns all objects of a given
// type.
//
// The selector as the following methods:
//
// - count: returns a selector which returns the number of objects
// - filter: returns a selector which returns the objects filtered by
//           a predicate (count, groupBy and sort can be chained)
// - find: returns a selector which returns the first object matching
//         a predicate
// - groupBy: returns a selector which returns the objects grouped by
//            a value determined by a getter selector
// - pick: returns a selector which returns only the objects with given
//         ids (groupBy and sort can be chained)
// - sort: returns a selector which returns the objects appropriately
//         sorted (groupBy can be chained)
export const createGetObjectsOfType = type => {
  const getObjects = state => state.objects.byType[type] || EMPTY_OBJECT

  return _extendCollectionSelector(createFilter(
    getObjects,
    _getPermissionsPredicate
  ), type)
}

// TODO: implement
export const createGetTags = () => {
  const getTags = () => EMPTY_OBJECT

  return _extendCollectionSelector(getTags, 'tag')
}

export const createGetObjectMessages = objectSelector =>
  createGetObjectsOfType('message').filter(
    create(
      objectSelector,
      ({ id }) => message => message.$object === id
    )
  ).sort()
<commit_msg>fix(selectors): fix pool ordering<commit_after>import checkPermissions from 'xo-acl-resolver'
import filter from 'lodash/filter'
import find from 'lodash/find'
import forEach from 'lodash/forEach'
import groupBy from 'lodash/groupBy'
import isArray from 'lodash/isArray'
import isArrayLike from 'lodash/isArrayLike'
import isFunction from 'lodash/isFunction'
import orderBy from 'lodash/orderBy'
import pickBy from 'lodash/pickBy'
import size from 'lodash/size'
import slice from 'lodash/slice'
import { createSelector as create } from 'reselect'

import shallowEqual from './shallow-equal'
import { EMPTY_ARRAY, EMPTY_OBJECT, invoke } from './utils'

// ===================================================================

export {
  // That's usually the name we want to import.
  createSelector,

  // But selectors.create is nice too :)
  createSelector as create
} from 'reselect'

// -------------------------------------------------------------------

// Wraps a function which returns a collection to returns the previous
// result if the collection has not really changed (ie still has the
// same items).
//
// Use case: in connect, to avoid rerendering a component where the
// objects are still the same.
const _createCollectionWrapper = selector => {
  let cache

  return (...args) => {
    const value = selector(...args)
    if (!shallowEqual(value, cache)) {
      cache = value
    }
    return cache
  }
}
export { _createCollectionWrapper as createCollectionWrapper }

const _SELECTOR_PLACEHOLDER = Symbol('selector placeholder')

// Experimental!
//
// Similar to reselect's createSelector() but inputs can be either
// selectors or plain values.
//
// To pass a function as a plain value, simply wrap it with an array.
const _create2 = (...inputs) => {
  const resultFn = inputs.pop()

  if (inputs.length === 1 && isArray(inputs[0])) {
    inputs = inputs[0]
  }

  const n = inputs.length

  const inputSelectors = []
  for (let i = 0; i < n; ++i) {
    const input = inputs[i]

    if (isFunction(input)) {
      inputSelectors.push(input)
      inputs[i] = _SELECTOR_PLACEHOLDER
    } else if (isArray(input) && input.length === 1) {
      inputs[i] = input[0]
    }
  }

  if (!inputSelectors.length) {
    throw new Error('no input selectors')
  }

  return create(inputSelectors, function () {
    const args = new Array(n)
    for (let i = 0, j = 0; i < n; ++i) {
      const input = inputs[i]
      args[i] = input === _SELECTOR_PLACEHOLDER
        ? arguments[j++]
        : input
    }

    return resultFn.apply(this, args)
  })
}

// ===================================================================
// Generic selector creators.

export const createCounter = (collection, predicate) =>
  _create2(
    collection,
    predicate,
    (collection, predicate) => {
      if (!predicate) {
        return size(collection)
      }

      let count = 0
      forEach(collection, item => {
        if (predicate(item)) {
          ++count
        }
      })
      return count
    }
  )

// Creates an object selector from an object selector and a properties
// selector.
//
// Should only be used with a reasonable number of properties.
export const createPicker = (object, props) =>
  _createCollectionWrapper(
    _create2(
      object, props,
      (object, props) => {
        const values = {}
        forEach(props, prop => {
          const value = object[prop]
          if (value) {
            values[prop] = value
          }
        })
        return values
      }
    )
  )

// Special cases:
// - predicate == null → no filtering
// - predicate === false → everything is filtered out
export const createFilter = (collection, predicate) =>
  _createCollectionWrapper(
    _create2(
      collection,
      predicate,
      (collection, predicate) => predicate === false
        ? (isArrayLike(collection) ? EMPTY_ARRAY : EMPTY_OBJECT)
        : predicate
          ? (isArrayLike(collection) ? filter : pickBy)(collection, predicate)
          : collection
    )
  )

export const createFinder = (collection, predicate) =>
  _create2(
    collection,
    predicate,
    find
  )

export const createGroupBy = (collection, getter) =>
  _create2(
    collection,
    getter,
    groupBy
  )

export const createPager = (array, page, n = 25) => _createCollectionWrapper(
  _create2(
    array,
    page,
    n,
    (array, page, n) => {
      const start = (page - 1) * n
      return slice(array, start, start + n)
    }
  )
)

export const createSort = (
  collection,
  getter = 'name_label',
  order = 'asc'
) => _create2(collection, getter, order, orderBy)

export const createTop = (collection, iteratee, n) =>
  _createCollectionWrapper(
    _create2(
      collection,
      iteratee,
      n,
      (objects, iteratee, n) => {
        let results = orderBy(objects, iteratee, 'desc')
        if (n < results.length) {
          results.length = n
        }
        return results
      }
    )
  )

// ===================================================================
// Root-ish selectors (no dependencies).

export const areObjectsFetched = state => state.objects.fetched

const _getId = (state, { routeParams, id }) => routeParams
  ? routeParams.id
  : id

export const getLang = state => state.lang

export const getUser = state => state.user

const _getPermissionsPredicate = invoke(() => {
  const getPredicate = create(
    state => state.permissions,
    state => state.objects,
    (permissions, objects) => {
      objects = objects.all
      const getObject = id => (objects[id] || EMPTY_OBJECT)

      return id => checkPermissions(permissions, getObject, id.id || id, 'view')
    }
  )

  return state => {
    const user = getUser(state)
    if (!user) {
      return false
    }

    if (user.permission === 'admin') {
      return // No predicate means no filtering.
    }

    return getPredicate(state)
  }
})

// ===================================================================
// Common selector creators.

// Creates an object selector from an id selector.
export const createGetObject = (idSelector = _getId) =>
  (state, props) => {
    const object = state.objects.all[idSelector(state, props)]
    if (!object) {
      return
    }

    const predicate = _getPermissionsPredicate(state)
    if (!predicate) {
      if (predicate == null) {
        return object // no filtering
      }

      // predicate is false.
      return
    }

    if (predicate(object)) {
      return object
    }
  }

// Specialized createSort() configured for a given type.
export const createSortForType = invoke(() => {
  const optionsByType = {
    message: [
      [ message => message.time ],
      'desc'
    ],
    PIF: [
      [ pif => pif.device ]
    ],
    pool: [
      [ pool => pool.name_label ]
    ],
    pool_patch: [
      [ patch => patch.name ]
    ],
    VBD: [
      [ vbd => vbd.position ]
    ],
    'VDI-snapshot': [
      [ snapshot => snapshot.snapshot_time ],
      'desc'
    ],
    'VM-snapshot': [
      [ snapshot => snapshot.snapshot_time ],
      'desc'
    ]
  }
  const defaults = [
    [ [ object => object.$pool, object => object.name_label ] ]
  ]
  const getOptions = type => optionsByType[type] || defaults

  return (type, collection) => createSort(collection, ...getOptions(type))
})

// Add utility methods to a collection selector.
const _extendCollectionSelector = (selector, objectsType) => {
  // Terminal methods.
  const _addCount = selector => {
    selector.count = predicate => createCounter(selector, predicate)
    return selector
  }
  _addCount(selector)
  const _addGroupBy = selector => {
    selector.groupBy = getter => createGroupBy(selector, getter)
    return selector
  }
  _addGroupBy(selector)
  selector.find = predicate => createFinder(selector, predicate)

  // groupBy can be chained.
  const _addSort = selector => {
    // TODO: maybe memoize when no idsSelector.
    selector.sort = () => _addGroupBy(createSortForType(objectsType, selector))
    return selector
  }
  _addSort(selector)

  // groupBy and sort can be chained.
  selector.pick = idsSelector => _addGroupBy(_addSort(
    createPicker(selector, idsSelector)
  ))

  // count, groupBy and sort can be chained.
  selector.filter = predicate => _addCount(_addGroupBy(_addSort(
    createFilter(selector, predicate)
  )))

  return selector
}

// Creates a collection selector which returns all objects of a given
// type.
//
// The selector as the following methods:
//
// - count: returns a selector which returns the number of objects
// - filter: returns a selector which returns the objects filtered by
//           a predicate (count, groupBy and sort can be chained)
// - find: returns a selector which returns the first object matching
//         a predicate
// - groupBy: returns a selector which returns the objects grouped by
//            a value determined by a getter selector
// - pick: returns a selector which returns only the objects with given
//         ids (groupBy and sort can be chained)
// - sort: returns a selector which returns the objects appropriately
//         sorted (groupBy can be chained)
export const createGetObjectsOfType = type => {
  const getObjects = state => state.objects.byType[type] || EMPTY_OBJECT

  return _extendCollectionSelector(createFilter(
    getObjects,
    _getPermissionsPredicate
  ), type)
}

// TODO: implement
export const createGetTags = () => {
  const getTags = () => EMPTY_OBJECT

  return _extendCollectionSelector(getTags, 'tag')
}

export const createGetObjectMessages = objectSelector =>
  createGetObjectsOfType('message').filter(
    create(
      objectSelector,
      ({ id }) => message => message.$object === id
    )
  ).sort()
<|endoftext|>


<commit_before>var bar = function() {
  var $barContainer
    , $barElement
    , $barTransformProperty
    , $barProgress
    , $barTimer
    , $hasTouch = 'createTouch' in document

  function init() {
    $barContainer = document.createElement('div')
    $barContainer.id = 'instantclick'
    $barElement = document.createElement('div')
    $barElement.id = 'instantclick-bar'
    $barElement.className = 'instantclick-bar'
    $barContainer.appendChild($barElement)

    var vendors = ['Webkit', 'Moz', 'O']

    $barTransformProperty = 'transform'
    if (!($barTransformProperty in $barElement.style)) {
      for (var i = 0; i < vendors.length; i++) {
        if (vendors[i] + 'Transform' in $barElement.style) {
          $barTransformProperty = vendors[i] + 'Transform'
        }
      }
    }

    var transitionProperty = 'transition'
    if (!(transitionProperty in $barElement.style)) {
      for (var i = 0; i < vendors.length; i++) {
        if (vendors[i] + 'Transition' in $barElement.style) {
          transitionProperty = '-' + vendors[i].toLowerCase() + '-' + transitionProperty
        }
      }
    }

    var style = document.createElement('style')
    style.innerHTML = '#instantclick{position:' + ($hasTouch ? 'absolute' : 'fixed') + ';top:0;left:0;width:100%;pointer-events:none;z-index:2147483647;' + transitionProperty + ':opacity .25s .1s}'
      + '.instantclick-bar{background:#29d;width:100%;margin-left:-100%;height:2px;' + transitionProperty + ':all .25s}'
    /* We set the bar's background in `.instantclick-bar` so that it can be
       overriden in CSS with `#instantclick-bar`, as IDs have higher priority.
    */
    document.head.appendChild(style)

    if ($hasTouch) {
      updatePositionAndScale()
      addEventListener('resize', updatePositionAndScale)
      addEventListener('scroll', updatePositionAndScale)
    }

  }

  function start(at, jump) {
    $barProgress = at
    if (document.getElementById($barContainer.id)) {
      document.body.removeChild($barContainer)
    }
    $barContainer.style.opacity = '1'
    if (document.getElementById($barContainer.id)) {
      document.body.removeChild($barContainer)
      /* So there's no CSS animation if already done once and it goes from 1 to 0 */
    }
    update()
    if (jump) {
      setTimeout(jumpStart, 0)
      /* Must be done in a timer, otherwise the CSS animation doesn't happen (I don't know why). */
    }
    clearTimeout($barTimer)
    $barTimer = setTimeout(inc, 500)
  }

  function jumpStart() {
    $barProgress = 10
    update()
  }

  function inc() {
    $barProgress += 1 + (Math.random() * 2)
    if ($barProgress >= 98) {
      $barProgress = 98
    }
    else {
      $barTimer = setTimeout(inc, 500)
    }
    update()
  }

  function update() {
    $barElement.style[$barTransformProperty] = 'translate(' + $barProgress + '%)'
    if (!document.getElementById($barContainer.id)) {
      document.body.appendChild($barContainer)
    }
  }

  function done() {
    if (document.getElementById($barContainer.id)) {
      clearTimeout($barTimer)
      $barProgress = 100
      update()
      $barContainer.style.opacity = '0'
      /* When you're debugging, setting this to 0.5 is handy. */
      return
    }

    /* The bar container hasn't been appended: It's a new page. */
    start($barProgress == 100 ? 0 : $barProgress)
    /* $barProgress is 100 on popstate, usually. */
    setTimeout(done, 0)
    /* Must be done in a timer, otherwise the CSS animation doesn't happen. */
  }

  function updatePositionAndScale() {
    /* Adapted from code by Sam Stephenson and Mislav Marohnic
       http://signalvnoise.com/posts/2407
    */

    $barContainer.style.left = pageXOffset + 'px'
    $barContainer.style.width = innerWidth + 'px'
    $barContainer.style.top = pageYOffset + 'px'

    var landscape = 'orientation' in window && Math.abs(orientation) == 90
      , scaleY = innerWidth / screen[landscape ? 'height' : 'width'] * 2
    /* We multiply the size by 2 because the progress bar is harder
       to notice on a mobile device.
    */
    $barContainer.style[$barTransformProperty] = 'scaleY(' + scaleY  + ')'
  }

  return {
    init: init,
    start: start,
    done: done
  }
}()
<commit_msg>Remove `bar` prefix from loading indicator’s global variables<commit_after>var bar = function() {
  var $container
    , $element
    , $transformProperty
    , $progress
    , $timer
    , $hasTouch = 'createTouch' in document

  function init() {
    $container = document.createElement('div')
    $container.id = 'instantclick'
    $element = document.createElement('div')
    $element.id = 'instantclick-bar'
    $element.className = 'instantclick-bar'
    $container.appendChild($element)

    var vendors = ['Webkit', 'Moz', 'O']

    $transformProperty = 'transform'
    if (!($transformProperty in $element.style)) {
      for (var i = 0; i < vendors.length; i++) {
        if (vendors[i] + 'Transform' in $element.style) {
          $transformProperty = vendors[i] + 'Transform'
        }
      }
    }

    var transitionProperty = 'transition'
    if (!(transitionProperty in $element.style)) {
      for (var i = 0; i < vendors.length; i++) {
        if (vendors[i] + 'Transition' in $element.style) {
          transitionProperty = '-' + vendors[i].toLowerCase() + '-' + transitionProperty
        }
      }
    }

    var style = document.createElement('style')
    style.innerHTML = '#instantclick{position:' + ($hasTouch ? 'absolute' : 'fixed') + ';top:0;left:0;width:100%;pointer-events:none;z-index:2147483647;' + transitionProperty + ':opacity .25s .1s}'
      + '.instantclick-bar{background:#29d;width:100%;margin-left:-100%;height:2px;' + transitionProperty + ':all .25s}'
    /* We set the bar's background in `.instantclick-bar` so that it can be
       overriden in CSS with `#instantclick-bar`, as IDs have higher priority.
    */
    document.head.appendChild(style)

    if ($hasTouch) {
      updatePositionAndScale()
      addEventListener('resize', updatePositionAndScale)
      addEventListener('scroll', updatePositionAndScale)
    }

  }

  function start(at, jump) {
    $progress = at
    if (document.getElementById($container.id)) {
      document.body.removeChild($container)
    }
    $container.style.opacity = '1'
    if (document.getElementById($container.id)) {
      document.body.removeChild($container)
      /* So there's no CSS animation if already done once and it goes from 1 to 0 */
    }
    update()
    if (jump) {
      setTimeout(jumpStart, 0)
      /* Must be done in a timer, otherwise the CSS animation doesn't happen (I don't know why). */
    }
    clearTimeout($timer)
    $timer = setTimeout(inc, 500)
  }

  function jumpStart() {
    $progress = 10
    update()
  }

  function inc() {
    $progress += 1 + (Math.random() * 2)
    if ($progress >= 98) {
      $progress = 98
    }
    else {
      $timer = setTimeout(inc, 500)
    }
    update()
  }

  function update() {
    $element.style[$transformProperty] = 'translate(' + $progress + '%)'
    if (!document.getElementById($container.id)) {
      document.body.appendChild($container)
    }
  }

  function done() {
    if (document.getElementById($container.id)) {
      clearTimeout($timer)
      $progress = 100
      update()
      $container.style.opacity = '0'
      /* When you're debugging, setting this to 0.5 is handy. */
      return
    }

    /* The bar container hasn't been appended: It's a new page. */
    start($progress == 100 ? 0 : $progress)
    /* $progress is 100 on popstate, usually. */
    setTimeout(done, 0)
    /* Must be done in a timer, otherwise the CSS animation doesn't happen. */
  }

  function updatePositionAndScale() {
    /* Adapted from code by Sam Stephenson and Mislav Marohnic
       http://signalvnoise.com/posts/2407
    */

    $container.style.left = pageXOffset + 'px'
    $container.style.width = innerWidth + 'px'
    $container.style.top = pageYOffset + 'px'

    var landscape = 'orientation' in window && Math.abs(orientation) == 90
      , scaleY = innerWidth / screen[landscape ? 'height' : 'width'] * 2
    /* We multiply the size by 2 because the progress bar is harder
       to notice on a mobile device.
    */
    $container.style[$transformProperty] = 'scaleY(' + scaleY  + ')'
  }

  return {
    init: init,
    start: start,
    done: done
  }
}()
<|endoftext|>


<commit_before>/*jshint node:true*/
module.exports = {
  description: '',

  // locals: function(options) {
  //   // Return custom template variables here.
  //   return {
  //     foo: options.entity.options.foo
  //   };
  // }

  afterInstall: function(options) {
    return this.addBowerPackageToProject('lightbox', '2.8.2');
  }
};
<commit_msg>https://github.com/ramybenaroya/ember-cli-lightbox/issues/10<commit_after>/*jshint node:true*/
module.exports = {
  normalizeEntityName: function() {},
  description: '',

  // locals: function(options) {
  //   // Return custom template variables here.
  //   return {
  //     foo: options.entity.options.foo
  //   };
  // }

  afterInstall: function(options) {
    return this.addBowerPackageToProject('lightbox', '2.8.2');
  }
};
<|endoftext|>


<commit_before>/**
 *
 * @author Daniel McDonald
 * @copyright Copyright 2014, biocore
 * @credits Daniel McDonald, Joshua Shorenstein, Jose Navas
 * @license BSD
 * @version 0.1.0-dev
 * @maintainer Daniel McDonald
 * @email mcdonadt@colorado.edu
 * @status Development
 *
 */

var moi_list = new function () {
    var info_ids = {};
    var info_list = null;
    var restrict_to_group_id = null;

    function createButton(context, func){
        var button = document.createElement("input");
        button.type = "button";
        button.value = "Remove";
        button.onclick = function () { func(context.getAttribute("id")) };
        context.appendChild(button);
    };

    function tracked_node(info) {
        return restrict_to_group_id == info['parent'];
    };

    function setResult(info) {
        if(!tracked_node(info))
            return;

        var results = document.createElement("a");
        results.href = info.url + '/' + info.id;
        results.innerHTML = info.status;

        var state_node = document.getElementById(info.id + ":status");
        var para_node = state_node.parentNode;
        var remove_node = para_node.lastChild;

        para_node.insertBefore(results, remove_node);
        para_node.removeChild(state_node);
    };

    function addInfo(info) {
        if(!tracked_node(info))
            return;

        if(info.id in info_ids)
            return;

        var para = document.createElement("p");
        para.setAttribute("id", info.id);
        var para_node = document.createTextNode(info.name + ': ');

        var state = document.createElement("span");
        state.setAttribute("id", info.id + ":status");

        if(info.type == 'job') {
            var state_node = document.createTextNode(info.status);
        } else {
            var state_node = document.createElement("a");
            state_node.href = info.url + '/' + info.id;
            state_node.innerHTML = 'Group details';
        }

        para.appendChild(para_node);
        state.appendChild(state_node);
        para.appendChild(state);
        createButton(para, function (id_to_drop) {
                                          moi.send("remove", [id_to_drop]);
                                          removeInfo(id_to_drop);
                                      });
        info_list.appendChild(para);

        if(info.type == 'job') {
            if((info.status == 'Success' || info.status == 'Failed') && info.url) {
                setResult(info);
            }
        }

        info_ids[info.id] = para;
    };

    function removeInfo(id) {
        // can't get here without the user clicking so tracking isn't relevant
        if(id in info_ids) {
            para_node = info_ids[id];
            info_list.removeChild(para_node);
            delete info_ids[id];
        }
    };

    function updateInfo(info) {
        if(!tracked_node(info))
            return;

        if((info.status == 'Success' || info.status == 'Failed') && info.url) {
            setResult(info);
        }
        else {
            status_msg = document.getElementById(info.id + ":status");

            if (!(status_msg == null)) {
                // if a result has been set, then the status is no more
                status_msg.innerHTML = info.status;
            }
        }
    };

    this.init = function (group_id) {
        restrict_to_group_id = group_id;
        info_list = document.createElement('div');
        info_list.setAttribute("id", "moi-list");
        document.body.appendChild(info_list);

        moi.add_callback('add', addInfo);
        moi.add_callback('get', addInfo);
        moi.add_callback('remove', removeInfo);
        moi.add_callback('update', updateInfo);
        moi.init(group_id);
    };
};
<commit_msg>optional div<commit_after>/**
 *
 * @author Daniel McDonald
 * @copyright Copyright 2014, biocore
 * @credits Daniel McDonald, Joshua Shorenstein, Jose Navas
 * @license BSD
 * @version 0.1.0-dev
 * @maintainer Daniel McDonald
 * @email mcdonadt@colorado.edu
 * @status Development
 *
 */

var moi_list = new function () {
    var info_ids = {};
    var info_list = null;
    var restrict_to_group_id = null;

    function createButton(context, func){
        var button = document.createElement("input");
        button.type = "button";
        button.value = "Remove";
        button.onclick = function () { func(context.getAttribute("id")) };
        context.appendChild(button);
    };

    function tracked_node(info) {
        return restrict_to_group_id == info['parent'];
    };

    function setResult(info) {
        if(!tracked_node(info))
            return;

        var results = document.createElement("a");
        results.href = info.url + '/' + info.id;
        results.innerHTML = info.status;

        var state_node = document.getElementById(info.id + ":status");
        var para_node = state_node.parentNode;
        var remove_node = para_node.lastChild;

        para_node.insertBefore(results, remove_node);
        para_node.removeChild(state_node);
    };

    function addInfo(info) {
        if(!tracked_node(info))
            return;

        if(info.id in info_ids)
            return;

        var para = document.createElement("p");
        para.setAttribute("id", info.id);
        var para_node = document.createTextNode(info.name + ': ');

        var state = document.createElement("span");
        state.setAttribute("id", info.id + ":status");

        if(info.type == 'job') {
            var state_node = document.createTextNode(info.status);
        } else {
            var state_node = document.createElement("a");
            state_node.href = info.url + '/' + info.id;
            state_node.innerHTML = 'Group details';
        }

        para.appendChild(para_node);
        state.appendChild(state_node);
        para.appendChild(state);
        createButton(para, function (id_to_drop) {
                                          moi.send("remove", [id_to_drop]);
                                          removeInfo(id_to_drop);
                                      });
        info_list.appendChild(para);

        if(info.type == 'job') {
            if((info.status == 'Success' || info.status == 'Failed') && info.url) {
                setResult(info);
            }
        }

        info_ids[info.id] = para;
    };

    function removeInfo(id) {
        // can't get here without the user clicking so tracking isn't relevant
        if(id in info_ids) {
            para_node = info_ids[id];
            info_list.removeChild(para_node);
            delete info_ids[id];
        }
    };

    function updateInfo(info) {
        if(!tracked_node(info))
            return;

        if((info.status == 'Success' || info.status == 'Failed') && info.url) {
            setResult(info);
        }
        else {
            status_msg = document.getElementById(info.id + ":status");

            if (!(status_msg == null)) {
                // if a result has been set, then the status is no more
                status_msg.innerHTML = info.status;
            }
        }
    };

    this.init = function (group_id, div) {
        if (typeof div === 'undefined') {
            div = 'div';
        }

        restrict_to_group_id = group_id;
        info_list = document.createElement(div);
        info_list.setAttribute("id", "moi-list");
        document.body.appendChild(info_list);

        moi.add_callback('add', addInfo);
        moi.add_callback('get', addInfo);
        moi.add_callback('remove', removeInfo);
        moi.add_callback('update', updateInfo);
        moi.init(group_id);
    };
};
<|endoftext|>


<commit_before>module.exports = chmodr
chmodr.sync = chmodrSync

var fs = require("fs")
, path = require("path")

function chmodr (p, mode, cb) {
  fs.readdir(p, function (er, children) {
    // any error other than ENOTDIR means it's not readable, or
    // doesn't exist.  give up.
    if (er && er.code !== "ENOTDIR") return cb(er)
    if (er || !children.length) return fs.chmod(p, mode, cb)

    var len = children.length
    , errState = null
    children.forEach(function (child) {
      chmodr(path.resolve(p, child), mode, then)
    })
    function then (er) {
      if (errState) return
      if (er) return cb(errState = er)
      if (-- len === 0) return fs.chmod(p, mode, cb)
    }
  })
}

function chmodrSync (p, mode) {
  var children
  try {
    children = fs.readdirSync(p)
  } catch (er) {
    if (er && er.code === "ENOTDIR") return fs.chmodSync(p, mode)
    throw er
  }
  if (!children.length) return fs.chmodSync(p, mode)

  children.forEach(function (child) {
    chmodrSync(path.resolve(p, child), mode)
  })
  return fs.chmodSync(p, mode)
}
<commit_msg>Never make directories non-listable<commit_after>module.exports = chmodr
chmodr.sync = chmodrSync

var fs = require("fs")
, path = require("path")

function chmodr (p, mode, cb) {
  fs.readdir(p, function (er, children) {
    // any error other than ENOTDIR means it's not readable, or
    // doesn't exist.  give up.
    if (er && er.code !== "ENOTDIR")
      return cb(er)
    var isDir = !er
    var m = isDir ? dirMode(mode) : mode
    if (er || !children.length)
      return fs.chmod(p, m, cb)

    var len = children.length
    var errState = null
    children.forEach(function (child) {
      chmodr(path.resolve(p, child), mode, then)
    })
    function then (er) {
      if (errState) return
      if (er) return cb(errState = er)
      if (-- len === 0) return fs.chmod(p, dirMode(mode), cb)
    }
  })
}

function chmodrSync (p, mode) {
  var children
  try {
    children = fs.readdirSync(p)
  } catch (er) {
    if (er && er.code === "ENOTDIR") return fs.chmodSync(p, mode)
    throw er
  }
  if (!children.length) return fs.chmodSync(p, dirMode(mode))

  children.forEach(function (child) {
    chmodrSync(path.resolve(p, child), mode)
  })
  return fs.chmodSync(p, dirMode(mode))
}

// If a party has r, add x
// so that dirs are listable
function dirMode(mode) {
  if (mode & 0400) mode |= 0100
  if (mode & 040) mode |= 010
  if (mode & 04) mode |= 01
  return mode
}
<|endoftext|>


<commit_before>VMX.config.useMagicCanvas = true;
VMX.callback = function(detections){
  var mc = VMX.getMagicCanvas();
  var mcc = mc.getContext('2d');
  console.log(detections);
}

<commit_msg>adding ledger<commit_after>VMX.config.useMagicCanvas = true;
VMX.callback = function(detections){
  if (!mc = VMX.getMagicCanvas()){
    return; //canvas not open
  };
  var modelName = detections[0].cls;

  var mcc = mc.getContext('2d');

  for(var x in detections){
    if(detections[x].score > MIN_CONFIDENCE){
      VMX.storage.ledger[modelName] = (x+1);
    } else {
      break;
    }
  }

  console.log(VMX.storage.ledger);
}

<|endoftext|>


<commit_before>'use strict';

var Mailgun = require('mailgun-js');
var packageData = require('../package.json');
var pickBy = require('lodash.pickby');
var some = require('lodash.some');

var whitelistExact = [
  'from',
  'to',
  'cc',
  'bcc',
  'subject',
  'text',
  'html',
  'attachment',
  'inline',
  'o:tag',
  'o:campaign',
  'o:dkim',
  'o:deliverytime',
  'o:testmode',
  'o:tracking',
  'o:tracking-clicks',
  'o:tracking-opens',
  'o:require-tls',
  'o:skip-verification'
];
var whitelistPrefix = [
  'h:',
  'v:'
];

module.exports = function (options) {
  return new MailgunTransport(options);
};

function MailgunTransport(options) {
  this.options = options || {};
  this.name = 'Mailgun';
  this.version = packageData.version;

  this.mailgun = Mailgun({
    apiKey: this.options.auth.api_key,
    domain: this.options.auth.domain || ''
  });
}

MailgunTransport.prototype.send = function send(mail, callback) {
  var mailData = mail.data;
  // convert nodemailer attachments to mailgun-js attachements
  if(mailData.attachments){
    var a, b, aa = [];
    for(var i in mailData.attachments){
      a = mailData.attachments[i];
      b = new this.mailgun.Attachment({
        data        : a.path || undefined,
        filename    : a.filename || undefined,
        contentType : a.contentType || undefined,
        knownLength : a.knownLength || undefined
      });

      aa.push(b);
    }
    mailData.attachment = aa;
  }

  var options = pickBy(mailData, function (key) {
    if (whitelistExact.indexOf(key) !== -1) {
      return true;
    }

    return some(whitelistPrefix, function (prefix) {
      return key.startsWith(prefix);
    });
  });

  this.mailgun.messages().send(options, function (err, data) {
    callback(err || null, data);
  });

};

<commit_msg>inline is not supported from nodemailer<commit_after>'use strict';

var Mailgun = require('mailgun-js');
var packageData = require('../package.json');
var pickBy = require('lodash.pickby');
var some = require('lodash.some');

var whitelistExact = [
  'from',
  'to',
  'cc',
  'bcc',
  'subject',
  'text',
  'html',
  'attachment',
  'o:tag',
  'o:campaign',
  'o:dkim',
  'o:deliverytime',
  'o:testmode',
  'o:tracking',
  'o:tracking-clicks',
  'o:tracking-opens',
  'o:require-tls',
  'o:skip-verification'
];
var whitelistPrefix = [
  'h:',
  'v:'
];

module.exports = function (options) {
  return new MailgunTransport(options);
};

function MailgunTransport(options) {
  this.options = options || {};
  this.name = 'Mailgun';
  this.version = packageData.version;

  this.mailgun = Mailgun({
    apiKey: this.options.auth.api_key,
    domain: this.options.auth.domain || ''
  });
}

MailgunTransport.prototype.send = function send(mail, callback) {
  var mailData = mail.data;
  // convert nodemailer attachments to mailgun-js attachements
  if(mailData.attachments){
    var a, b, aa = [];
    for(var i in mailData.attachments){
      a = mailData.attachments[i];
      b = new this.mailgun.Attachment({
        data        : a.path || undefined,
        filename    : a.filename || undefined,
        contentType : a.contentType || undefined,
        knownLength : a.knownLength || undefined
      });

      aa.push(b);
    }
    mailData.attachment = aa;
  }

  var options = pickBy(mailData, function (key) {
    if (whitelistExact.indexOf(key) !== -1) {
      return true;
    }

    return some(whitelistPrefix, function (prefix) {
      return key.startsWith(prefix);
    });
  });

  this.mailgun.messages().send(options, function (err, data) {
    callback(err || null, data);
  });

};

<|endoftext|>


<commit_before><commit_msg>require https module<commit_after>#!/usr/bin/env node
var https = require('https')<|endoftext|>