<commit_before>/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import sdk from "../../../index";
import MatrixClientPeg from "../../../MatrixClientPeg";
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap";
import {RoomMember} from "matrix-js-sdk/lib/matrix";
import * as humanize from "humanize";
import SdkConfig from "../../../SdkConfig";
import {htmlEntitiesEncode} from "../../../HtmlUtils";

// TODO: [TravisR] Make this generic for all kinds of invites

const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked

class DMRoomTile extends React.PureComponent {
    static propTypes = {
        member: PropTypes.object.isRequired,
        lastActiveTs: PropTypes.number,
        onToggle: PropTypes.func.isRequired,
        highlightWord: PropTypes.string,
    };

    _onClick = (e) => {
        // Stop the browser from highlighting text
        e.preventDefault();
        e.stopPropagation();

        this.props.onToggle(this.props.member.userId);
    };

    _highlightName(str: string) {
        if (!this.props.highlightWord) return str;

        // First encode the thing to avoid injection
        str = htmlEntitiesEncode(str);

        // We convert things to lowercase for index searching, but pull substrings from
        // the submitted text to preserve case.
        const lowerStr = str.toLowerCase();
        const filterStr = this.props.highlightWord.toLowerCase();

        const result = [];

        let i = 0;
        let ii;
        while ((ii = lowerStr.indexOf(filterStr, i)) >= 0) {
            // Push any text we missed (first bit/middle of text)
            if (ii > i) {
                // Push any text we aren't highlighting (middle of text match)
                result.push({str.substring(i, ii)});
            }

            i = ii; // copy over ii only if we have a match (to preserve i for end-of-text matching)

            // Highlight the word the user entered
            const substr = str.substring(i, filterStr.length + i);
            result.push({substr});
            i += substr.length;
        }

        // Push any text we missed (end of text)
        if (i < (str.length - 1)) {
            result.push({str.substring(i)});
        }

        return result;
    }

    render() {
        const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");

        let timestamp = null;
        if (this.props.lastActiveTs) {
            // TODO: [TravisR] Figure out how to i18n this
            // `humanize` wants seconds for a timestamp, so divide by 1000
            const humanTs = humanize.relativeTime(this.props.lastActiveTs / 1000);
            timestamp = {humanTs};
        }

        return (

                <MemberAvatar member={this.props.member} width={36} height={36} />
                {this._highlightName(this.props.member.name)}
                {this._highlightName(this.props.member.userId)}
                {timestamp}

        );
    }
}

export default class DMInviteDialog extends React.PureComponent {
    static propTypes = {
        // Takes an array of user IDs/emails to invite.
        onFinished: PropTypes.func.isRequired,
    };

    constructor() {
        super();

        this.state = {
            targets: [], // string[] of mxids/email addresses
            filterText: "",
            recents: this._buildRecents(),
            numRecentsShown: INITIAL_ROOMS_SHOWN,
            suggestions: this._buildSuggestions(),
            numSuggestionsShown: INITIAL_ROOMS_SHOWN,
        };
    }

    _buildRecents(): {userId: string, user: RoomMember, lastActive: number} {
        const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
        const recents = [];
        for (const userId in rooms) {
            const room = rooms[userId];
            const member = room.getMember(userId);
            if (!member) continue; // just skip people who don't have memberships for some reason

            const lastEventTs = room.timeline && room.timeline.length
                ? room.timeline[room.timeline.length - 1].getTs()
                : 0;
            if (!lastEventTs) continue; // something weird is going on with this room

            recents.push({userId, user: member, lastActive: lastEventTs});
        }

        // Sort the recents by last active to save us time later
        recents.sort((a, b) => b.lastActive - a.lastActive);

        return recents;
    }

    _buildSuggestions(): {userId: string, user: RoomMember} {
        const maxConsideredMembers = 200;
        const client = MatrixClientPeg.get();
        const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']];
        const joinedRooms = client.getRooms()
            .filter(r => r.getMyMembership() === 'join')
            .filter(r => r.getJoinedMemberCount() <= maxConsideredMembers);
        const memberRooms = joinedRooms.reduce((members, room) => {
            const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId));
            for (const member of joinedMembers) {
                if (!members[member.userId]) {
                    members[member.userId] = {
                        member: member,
                        // Track the room size of the 'picked' member so we can use the profile of
                        // the smallest room (likely a DM).
                        pickedMemberRoomSize: room.getJoinedMemberCount(),
                        rooms: [],
                    };
                }

                members[member.userId].rooms.push(room);

                if (room.getJoinedMemberCount() < members[member.userId].pickedMemberRoomSize) {
                    members[member.userId].member = member;
                    members[member.userId].pickedMemberRoomSize = room.getJoinedMemberCount();
                }
            }
            return members;
        }, {/* userId => {member, rooms[]} */});
        const memberScores = Object.values(memberRooms).reduce((scores, entry) => {
            const numMembersTotal = entry.rooms.reduce((c, r) => c + r.getJoinedMemberCount(), 0);
            const maxRange = maxConsideredMembers * entry.rooms.length;
            scores[entry.member.userId] = {
                member: entry.member,
                numRooms: entry.rooms.length,
                score: Math.max(0, Math.pow(1 - (numMembersTotal / maxRange), 5)),
            };
            return scores;
        }, {/* userId => {member, numRooms, score} */});
        const members = Object.values(memberScores);
        members.sort((a, b) => {
            if (a.score === b.score) {
                if (a.numRooms === b.numRooms) {
                    return a.member.userId.localeCompare(b.member.userId);
                }

                return b.numRooms - a.numRooms;
            }
            return b.score - a.score;
        });
        return members.map(m => ({userId: m.member.userId, user: m.member}));
    }

    _startDm = () => {
        this.props.onFinished(this.state.targets);
    };

    _cancel = () => {
        this.props.onFinished([]);
    };

    _updateFilter = (e) => {
        this.setState({filterText: e.target.value});
    };

    _showMoreRecents = () => {
        this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
    };

    _showMoreSuggestions = () => {
        this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN});
    };

    _toggleMember = (userId) => {
        const targets = this.state.targets.map(t => t); // cheap clone for mutation
        const idx = targets.indexOf(userId);
        if (idx >= 0) targets.splice(idx, 1);
        else targets.push(userId);
        this.setState({targets});
    };

    _renderSection(kind: "recents"|"suggestions") {
        let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
        let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
        const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
        const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
        const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");

        // Hide the section if there's nothing to filter by
        if (!sourceMembers || sourceMembers.length === 0) return null;

        // Do some simple filtering on the input before going much further. If we get no results, say so.
        if (this.state.filterText) {
            const filterBy = this.state.filterText.toLowerCase();
            sourceMembers = sourceMembers
                .filter(m => m.user.name.toLowerCase().includes(filterBy) || m.userId.toLowerCase().includes(filterBy));

            if (sourceMembers.length === 0) {
                return (

{sectionName}

{_t("No results")}

                );
            }
        }

        // If we're going to hide one member behind 'show more', just use up the space of the button
        // with the member's tile instead.
        if (showNum === sourceMembers.length - 1) showNum++;

        // .slice() will return an incomplete array but won't error on us if we go too far
        const toRender = sourceMembers.slice(0, showNum);
        const hasMore = toRender.length < sourceMembers.length;

        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
        let showMore = null;
        if (hasMore) {
            showMore = (
                <AccessibleButton onClick={showMoreFn} kind="link">
                    {_t("Show more")}
                </AccessibleButton>
            );
        }

        const tiles = toRender.map(r => (
            <DMRoomTile
                member={r.user}
                lastActiveTs={lastActive(r)}
                key={r.userId}
                onToggle={this._toggleMember}
                highlightWord={this.state.filterText}
            />
        ));
        return (

{sectionName}

                {tiles}
                {showMore}

        );
    }

    render() {
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
        const Field = sdk.getComponent("elements.Field");
        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");

        // Dev note: The use of Field is temporary/incomplete pending https://github.com/vector-im/riot-web/issues/11197
        // For now, we just list who the targets are.
        const editor = (

                <Field
                    id="inviteTargets"
                    value={this.state.filterText}
                    onChange={this._updateFilter}
                />

        );
        const targets = this.state.targets.map(t =>
{t}
);

        const userId = MatrixClientPeg.get().getUserId();
        return (
            <BaseDialog
                className='mx_DMInviteDialog'
                hasCancel={true}
                onFinished={this._cancel}
                title={_t("Direct Messages")}
            >

                        {_t(
                            "If you can't find someone, ask them for their username, or share your " +
                            "username (%(userId)s) or profile link.",
                            {userId},
                            {a: (sub) => {sub}},
                        )}

                    {targets}

                        {editor}
                        <AccessibleButton
                            kind="primary"
                            onClick={this._startDm}
                            className='mx_DMInviteDialog_goButton'
                        >
                            {_t("Go")}
                        </AccessibleButton>

                    {this._renderSection('recents')}
                    {this._renderSection('suggestions')}

            </BaseDialog>
        );
    }
}
<commit_msg>Support searching in the user directory for invite targets<commit_after>/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import sdk from "../../../index";
import MatrixClientPeg from "../../../MatrixClientPeg";
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap";
import {RoomMember} from "matrix-js-sdk/lib/matrix";
import * as humanize from "humanize";
import SdkConfig from "../../../SdkConfig";
import {htmlEntitiesEncode} from "../../../HtmlUtils";
import {getHttpUriForMxc} from "matrix-js-sdk/lib/content-repo";

// TODO: [TravisR] Make this generic for all kinds of invites

const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked

class DirectoryMember {
    _userId: string;
    _displayName: string;
    _avatarUrl: string;

    constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) {
        this._userId = userDirResult.user_id;
        this._displayName = userDirResult.display_name;
        this._avatarUrl = userDirResult.avatar_url;
    }

    // These next members are to implement the contract expected by DMRoomTile
    get name(): string {
        return this._displayName || this._userId;
    }

    get userId(): string {
        return this._userId;
    }

    getMxcAvatarUrl(): string {
        return this._avatarUrl;
    }
}

class DMRoomTile extends React.PureComponent {
    static propTypes = {
        // Has properties to match RoomMember: userId (str), name (str), getMxcAvatarUrl(): string
        member: PropTypes.object.isRequired,
        lastActiveTs: PropTypes.number,
        onToggle: PropTypes.func.isRequired,
        highlightWord: PropTypes.string,
    };

    _onClick = (e) => {
        // Stop the browser from highlighting text
        e.preventDefault();
        e.stopPropagation();

        this.props.onToggle(this.props.member.userId);
    };

    _highlightName(str: string) {
        if (!this.props.highlightWord) return str;

        // First encode the thing to avoid injection
        str = htmlEntitiesEncode(str);

        // We convert things to lowercase for index searching, but pull substrings from
        // the submitted text to preserve case.
        const lowerStr = str.toLowerCase();
        const filterStr = this.props.highlightWord.toLowerCase();

        const result = [];

        let i = 0;
        let ii;
        while ((ii = lowerStr.indexOf(filterStr, i)) >= 0) {
            // Push any text we missed (first bit/middle of text)
            if (ii > i) {
                // Push any text we aren't highlighting (middle of text match)
                result.push({str.substring(i, ii)});
            }

            i = ii; // copy over ii only if we have a match (to preserve i for end-of-text matching)

            // Highlight the word the user entered
            const substr = str.substring(i, filterStr.length + i);
            result.push({substr});
            i += substr.length;
        }

        // Push any text we missed (end of text)
        if (i < (str.length - 1)) {
            result.push({str.substring(i)});
        }

        return result;
    }

    render() {
        const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");

        let timestamp = null;
        if (this.props.lastActiveTs) {
            // TODO: [TravisR] Figure out how to i18n this
            // `humanize` wants seconds for a timestamp, so divide by 1000
            const humanTs = humanize.relativeTime(this.props.lastActiveTs / 1000);
            timestamp = {humanTs};
        }

        const avatarSize = 36;
        const avatarUrl = getHttpUriForMxc(
            MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(),
            avatarSize, avatarSize, "crop");

        return (

                <BaseAvatar
                    url={avatarUrl}
                    name={this.props.member.name}
                    idName={this.props.member.userId}
                    width={avatarSize}
                    height={avatarSize}
                />
                {this._highlightName(this.props.member.name)}
                {this._highlightName(this.props.member.userId)}
                {timestamp}

        );
    }
}

export default class DMInviteDialog extends React.PureComponent {
    static propTypes = {
        // Takes an array of user IDs/emails to invite.
        onFinished: PropTypes.func.isRequired,
    };

    _debounceTimer: number = null;

    constructor() {
        super();

        this.state = {
            targets: [], // string[] of mxids/email addresses
            filterText: "",
            recents: this._buildRecents(),
            numRecentsShown: INITIAL_ROOMS_SHOWN,
            suggestions: this._buildSuggestions(),
            numSuggestionsShown: INITIAL_ROOMS_SHOWN,
            serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions
        };
    }

    _buildRecents(): {userId: string, user: RoomMember, lastActive: number} {
        const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
        const recents = [];
        for (const userId in rooms) {
            const room = rooms[userId];
            const member = room.getMember(userId);
            if (!member) continue; // just skip people who don't have memberships for some reason

            const lastEventTs = room.timeline && room.timeline.length
                ? room.timeline[room.timeline.length - 1].getTs()
                : 0;
            if (!lastEventTs) continue; // something weird is going on with this room

            recents.push({userId, user: member, lastActive: lastEventTs});
        }

        // Sort the recents by last active to save us time later
        recents.sort((a, b) => b.lastActive - a.lastActive);

        return recents;
    }

    _buildSuggestions(): {userId: string, user: RoomMember} {
        const maxConsideredMembers = 200;
        const client = MatrixClientPeg.get();
        const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']];
        const joinedRooms = client.getRooms()
            .filter(r => r.getMyMembership() === 'join')
            .filter(r => r.getJoinedMemberCount() <= maxConsideredMembers);
        const memberRooms = joinedRooms.reduce((members, room) => {
            const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId));
            for (const member of joinedMembers) {
                if (!members[member.userId]) {
                    members[member.userId] = {
                        member: member,
                        // Track the room size of the 'picked' member so we can use the profile of
                        // the smallest room (likely a DM).
                        pickedMemberRoomSize: room.getJoinedMemberCount(),
                        rooms: [],
                    };
                }

                members[member.userId].rooms.push(room);

                if (room.getJoinedMemberCount() < members[member.userId].pickedMemberRoomSize) {
                    members[member.userId].member = member;
                    members[member.userId].pickedMemberRoomSize = room.getJoinedMemberCount();
                }
            }
            return members;
        }, {/* userId => {member, rooms[]} */});
        const memberScores = Object.values(memberRooms).reduce((scores, entry) => {
            const numMembersTotal = entry.rooms.reduce((c, r) => c + r.getJoinedMemberCount(), 0);
            const maxRange = maxConsideredMembers * entry.rooms.length;
            scores[entry.member.userId] = {
                member: entry.member,
                numRooms: entry.rooms.length,
                score: Math.max(0, Math.pow(1 - (numMembersTotal / maxRange), 5)),
            };
            return scores;
        }, {/* userId => {member, numRooms, score} */});
        const members = Object.values(memberScores);
        members.sort((a, b) => {
            if (a.score === b.score) {
                if (a.numRooms === b.numRooms) {
                    return a.member.userId.localeCompare(b.member.userId);
                }

                return b.numRooms - a.numRooms;
            }
            return b.score - a.score;
        });
        return members.map(m => ({userId: m.member.userId, user: m.member}));
    }

    _startDm = () => {
        this.props.onFinished(this.state.targets);
    };

    _cancel = () => {
        this.props.onFinished([]);
    };

    _updateFilter = (e) => {
        const term = e.target.value;
        this.setState({filterText: term});

        // Debounce server lookups to reduce spam. We don't clear the existing server
        // results because they might still be vaguely accurate, likewise for races which
        // could happen here.
        if (this._debounceTimer) {
            clearTimeout(this._debounceTimer);
        }
        this._debounceTimer = setTimeout(() => {
            MatrixClientPeg.get().searchUserDirectory({term}).then(r => {
                if (term !== this.state.filterText) {
                    // Discard the results - we were probably too slow on the server-side to make
                    // these results useful. This is a race we want to avoid because we could overwrite
                    // more accurate results.
                    return;
                }
                this.setState({
                    serverResultsMixin: r.results.map(u => ({
                        userId: u.user_id,
                        user: new DirectoryMember(u),
                    })),
                });
            }).catch(e => {
                console.error("Error searching user directory:");
                console.error(e);
                this.setState({serverResultsMixin: []}); // clear results because it's moderately fatal
            });
        }, 150); // 150ms debounce (human reaction time + some)
    };

    _showMoreRecents = () => {
        this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
    };

    _showMoreSuggestions = () => {
        this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN});
    };

    _toggleMember = (userId) => {
        const targets = this.state.targets.map(t => t); // cheap clone for mutation
        const idx = targets.indexOf(userId);
        if (idx >= 0) targets.splice(idx, 1);
        else targets.push(userId);
        this.setState({targets});
    };

    _renderSection(kind: "recents"|"suggestions") {
        let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
        let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
        const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
        const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
        const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");

        // Mix in the server results if we have any, but only if we're searching
        if (this.state.filterText && this.state.serverResultsMixin && kind === 'suggestions') {
            // only pick out the server results that aren't already covered though
            const uniqueServerResults = this.state.serverResultsMixin
                .filter(u => !sourceMembers.some(m => m.userId === u.userId));

            sourceMembers = sourceMembers.concat(uniqueServerResults);
        }

        // Hide the section if there's nothing to filter by
        if (!sourceMembers || sourceMembers.length === 0) return null;

        // Do some simple filtering on the input before going much further. If we get no results, say so.
        if (this.state.filterText) {
            const filterBy = this.state.filterText.toLowerCase();
            sourceMembers = sourceMembers
                .filter(m => m.user.name.toLowerCase().includes(filterBy) || m.userId.toLowerCase().includes(filterBy));

            if (sourceMembers.length === 0) {
                return (

{sectionName}

{_t("No results")}

                );
            }
        }

        // If we're going to hide one member behind 'show more', just use up the space of the button
        // with the member's tile instead.
        if (showNum === sourceMembers.length - 1) showNum++;

        // .slice() will return an incomplete array but won't error on us if we go too far
        const toRender = sourceMembers.slice(0, showNum);
        const hasMore = toRender.length < sourceMembers.length;

        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
        let showMore = null;
        if (hasMore) {
            showMore = (
                <AccessibleButton onClick={showMoreFn} kind="link">
                    {_t("Show more")}
                </AccessibleButton>
            );
        }

        const tiles = toRender.map(r => (
            <DMRoomTile
                member={r.user}
                lastActiveTs={lastActive(r)}
                key={r.userId}
                onToggle={this._toggleMember}
                highlightWord={this.state.filterText}
            />
        ));
        return (

{sectionName}

                {tiles}
                {showMore}

        );
    }

    render() {
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
        const Field = sdk.getComponent("elements.Field");
        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");

        // Dev note: The use of Field is temporary/incomplete pending https://github.com/vector-im/riot-web/issues/11197
        // For now, we just list who the targets are.
        const editor = (

                <Field
                    id="inviteTargets"
                    value={this.state.filterText}
                    onChange={this._updateFilter}
                />

        );
        const targets = this.state.targets.map(t =>
{t}
);

        const userId = MatrixClientPeg.get().getUserId();
        return (
            <BaseDialog
                className='mx_DMInviteDialog'
                hasCancel={true}
                onFinished={this._cancel}
                title={_t("Direct Messages")}
            >

                        {_t(
                            "If you can't find someone, ask them for their username, or share your " +
                            "username (%(userId)s) or profile link.",
                            {userId},
                            {a: (sub) => {sub}},
                        )}

                    {targets}

                        {editor}
                        <AccessibleButton
                            kind="primary"
                            onClick={this._startDm}
                            className='mx_DMInviteDialog_goButton'
                        >
                            {_t("Go")}
                        </AccessibleButton>

                    {this._renderSection('recents')}
                    {this._renderSection('suggestions')}

            </BaseDialog>
        );
    }
}
<|endoftext|>


<commit_before>goog.provide('rethinkdb.Connection');

goog.require('rethinkdb.errors');
goog.require('goog.math.Long');
goog.require('goog.proto2.WireFormatSerializer');

/**
 * As this is an abstract class you cannot use this constructor directly.
 * Construct instances of TcpConnection or HttpConnection instead.
 * @class A connection over which queries may be sent.
 * This is an abstract base class for two different kinds of connections,
 * a tcp connection or an http connection (for use by browsers).
 * @param {?string|Object} host Either a string giving the host to connect to or
 *      an object specifying host and/or port and/or db for the default database to
 *      use on this connection. Any key not supplied will revert to the default.
 * @param {?function(Error)=} opt_errorHandler optional error handler to use for this connection
 * @constructor
 */
rethinkdb.Connection = function(host, opt_errorHandler) {
    if (typeof host === undefined) {
        host = {};
    } else if (typeof host === 'string') {
        host = {'host':host};
    }

    this.host_ = host['host'] || this.DEFAULT_HOST;
    this.port_ = host['port'] || this.DEFAULT_PORT;
    this.db_   = host['db']   || this.DEFAULT_DB;

    typeCheck_(this.host_, 'string');
    typeCheck_(this.port_, 'number');
    typeCheck_(this.db_,   'string');

    this.outstandingQueries_ = {};
    this.nextToken_ = 1;

    this.errorHandler_ = opt_errorHandler || null;

    rethinkdb.last_connection_ = this;
};

/**
 * The default host to use for new connections that don't specify a host
 * @constant
 * @type {string}
 */
rethinkdb.Connection.prototype.DEFAULT_HOST = 'localhost';

/**
 * The default port to use for new connections that don't specify a port
 * @type {number}
 */
rethinkdb.Connection.prototype.DEFAULT_PORT = 12346;

/**
 * The default database to use for new connections that don't specify one
 * @constant
 * @type {string}
 */
rethinkdb.Connection.prototype.DEFAULT_DB = 'test';

/**
 * Closes this connection and reopens a new connection to the same host
 * and port.
 * @param {function(...)=} onConnect
 */
rethinkdb.Connection.prototype.reconnect = function(onConnect) {
    this.constructor.call(this, {'host':this.host_,
                                 'port':this.port_,
                                 'db'  :this.db_}, onConnect, this.errorHandler_);
};
goog.exportProperty(rethinkdb.Connection.prototype, 'reconnect',
                    rethinkdb.Connection.prototype.reconnect);

/**
 * Invoke the error handler on this connection.
 * @param {Error} error The origional error thrown
 * @ignore
 */
rethinkdb.Connection.prototype.error_ = function(error) {
    if (this.errorHandler_) {
        this.errorHandler_(error);
    } else {
        // Simply rethrow the error. It'll go uncaught and show up in the developer's console.

        // THIS IS A HACAK, but Slava wants it
        if (this.printErr_) {
            console.log(error.name + ":" + error.message);
        } else {
            throw error;
        }
    }
};

/**
 * Set the error handler to use for this connection. Will be called with any error
 * returned by the server.
 * @param {function(Error)} handler The error handler to use
 */
rethinkdb.Connection.prototype.setErrorHandler = function(handler) {
    this.errorHandler_ = handler;
};
goog.exportProperty(rethinkdb.Connection.prototype, 'setErrorHandler',
                    rethinkdb.Connection.prototype.setErrorHandler);

/**
 * Send the protobuf over the underlying connection. Implemented by subclasses.
 * @param {ArrayBuffer} data The protocol buffer to send
 * @private
 */
rethinkdb.Connection.prototype.send_ = goog.abstractMethod;

/**
 * Close the underlying connection. Implemented by subclass.
 * @private
 */
rethinkdb.Connection.prototype.close = goog.abstractMethod;

/**
 * Construct and run the given query, used by public run and iter methods
 * @param {rethinkdb.Query} expr The expression to run
 * @param {boolean} iterate Iterate callback over results
 * @param {function(...)} opt_callback Callback to which results are returned
 * @param {function(...)=} opt_doneCallback called when stream has ended.
 *  Only valid if iterate = true
 * @private
 */
rethinkdb.Connection.prototype.run_ = function(expr, iterate, opt_callback, opt_doneCallback) {
    var query = expr.buildQuery();

    // Assign a token
    query.setToken((this.nextToken_++).toString());

    this.outstandingQueries_[query.getToken()] = {
        callback: (opt_callback || null),
        query: expr, // Save origional ast for backtrace reconstructions
        iterate : iterate,
        done: (opt_doneCallback || null),
        partial : []
    };

    this.sendProtoBuf_(query);
};

/**
 * Serializes and sends protocol buffer object on this connection.
 * @param {goog.proto2.Message} pbObj
 * @private
 */
rethinkdb.Connection.prototype.sendProtoBuf_ = function(pbObj) {
    var serializer = new goog.proto2.WireFormatSerializer();
    var data = serializer.serialize(pbObj);

    // RDB protocol requires a 4 byte little endian length field at the head of the request
    var length = data.byteLength;
    var finalArray = new Uint8Array(length + 4);
    (new DataView(finalArray.buffer)).setInt32(0, length, true);
    finalArray.set(data, 4);

    this.send_(finalArray.buffer);
};

/**
 * Evaluates the given ReQL expression on the server and invokes
 * callback with the result.
 * @param {rethinkdb.Query} expr The expression to run.
 * @param {function(...)} opt_callback Function to invoke with response.
 */
rethinkdb.Connection.prototype.run = function(expr, opt_callback) {
    // THIS IS A HACAK, but Slava wants it
    this.printErr_ = false;

    argCheck_(arguments, 1);
    typeCheck_(expr, rethinkdb.Query);
    typeCheck_(opt_callback, 'function');
    this.run_(expr, false, opt_callback);
};
goog.exportProperty(rethinkdb.Connection.prototype, 'run',
                    rethinkdb.Connection.prototype.run);

/**
 * Version of run that prints the result rather than taking a callback
 * @param {rethinkdb.Query} expr The expression to run.
 */
rethinkdb.Connection.prototype.runp = function(expr) {
    // THIS IS A HACAK, but Slava wants it
    this.printErr_ = true;

    this.run_(expr, false, function(val) {
        console.log(val);
    });
};
goog.exportProperty(rethinkdb.Connection.prototype, 'runp',
                    rethinkdb.Connection.prototype.runp);

/**
 * Evaluates the given ReQL expression on the server and invokes
 * callback with each element of the result. The main advantage of using iter
 * over run is that results are lazily loaded as they are returned from the
 * server. Use anytime the result is expected to be very large.
 * @param {rethinkdb.Query} expr The expression to run.
 * @param {function(...)} opt_callback Function to invoke with response.
 * @param {function(...)=} opt_doneCallback Function to invoke when stream has ended.
 */
rethinkdb.Connection.prototype.iter = function(expr, opt_callback, opt_doneCallback) {
    // THIS IS A HACAK, but Slava wants it
    this.printErr_ = false;

    argCheck_(arguments, 1);
    typeCheck_(expr, rethinkdb.Query);
    typeCheck_(opt_callback, 'function');
    this.run_(expr, true, opt_callback, opt_doneCallback);
};
goog.exportProperty(rethinkdb.Connection.prototype, 'iter',
                    rethinkdb.Connection.prototype.iter);

/**
 * Called by subclass when data is received on the underlying connection.
 * @param {Uint8Array} data Received data from the connection
 * @private
 */
rethinkdb.Connection.prototype.recv_ = function(data) {
    goog.asserts.assert(data.length >= 4);
    var msgLength = (new DataView(data.buffer)).getUint32(0, true);
    if (msgLength !== (data.length - 4)) {
        this.error_(new rethinkdb.errors.ClientError("RDB response corrupted, length inaccurate"));
    }

    var serializer = new goog.proto2.WireFormatSerializer();
    var response = new Response();

    try {
        serializer.deserializeTo(response, new Uint8Array(data.buffer, 4));
    } catch(err) {
        this.error_(new rethinkdb.errors.ClientError("response deserialization failed"));
    }

    var responseStatus = response.getStatusCode();
    var request = this.outstandingQueries_[response.getToken()];
    if (request) {

        switch(responseStatus) {
        case Response.StatusCode.BROKEN_CLIENT:
            this.error_(new rethinkdb.errors.BrokenClient(response.getErrorMessage()));
            break;
        case Response.StatusCode.RUNTIME_ERROR:
            this.error_(new rethinkdb.errors.RuntimeError(formatServerError_(response, request.query)));
            break;
        case Response.StatusCode.BAD_QUERY:
            this.error_(new rethinkdb.errors.BadQuery(response.getErrorMessage()));
            break;
        case Response.StatusCode.SUCCESS_EMPTY:
            delete this.outstandingQueries_[response.getToken()]
            if (request.callback) request.callback();
            break;
        case Response.StatusCode.SUCCESS_STREAM:
            delete this.outstandingQueries_[response.getToken()]
            var results = response.responseArray().map(JSON.parse);
            if (request.callback) {
                if (request.iterate) {
                    results.forEach(request.callback);
                } else {
                    request.callback(request.partial.concat(results));
                }
            }

            if (request.done && request.iterate) request.done();
            break;
        case Response.StatusCode.SUCCESS_JSON:
            delete this.outstandingQueries_[response.getToken()]
            var result = JSON.parse(response.getResponse(0));
            if (request.iterate) {
                if (result.forEach && request.callback) {
                    result.forEach(request.callback);
                } else if (request.callback) {
                    request.callback(result);
                }
                if (request.done) {
                    request.done();
                }
            } else if (request.callback) {
                request.callback(result);
            }
            break;
        case Response.StatusCode.SUCCESS_PARTIAL:
            var cont = new Query();
            cont.setType(Query.QueryType.CONTINUE);
            cont.setToken(response.getTokenOrDefault());
            this.sendProtoBuf_(cont);

            var results = response.responseArray().map(JSON.parse);
            if (request.callback) {
                if (request.iterate) {
                    results.forEach(request.callback);
                } else {
                    // Save results for eventual callback call
                    request.partial = request.partial.concat(results);
                }
            }
            break;
        default:
            this.error_(new rethinkdb.errors.ClientError("unknown response status code"));
            break;
        }
    } // else no matching request
};

/**
 * Set the default db to use for this connection
 * @param {string} dbName
 */
rethinkdb.Connection.prototype.use = function(dbName) {
    argCheck_(arguments, 1);
    typeCheck_(dbName, 'string');
	this.db_ = dbName;
};

/**
 * Return the current default db for this connection
 * @return {string}
 */
rethinkdb.Connection.prototype.getDefaultDb = function() {
    return this.db_;
};
<commit_msg>js client, fixes small bug in connection<commit_after>goog.provide('rethinkdb.Connection');

goog.require('rethinkdb.errors');
goog.require('goog.math.Long');
goog.require('goog.proto2.WireFormatSerializer');

/**
 * As this is an abstract class you cannot use this constructor directly.
 * Construct instances of TcpConnection or HttpConnection instead.
 * @class A connection over which queries may be sent.
 * This is an abstract base class for two different kinds of connections,
 * a tcp connection or an http connection (for use by browsers).
 * @param {?string|Object} host Either a string giving the host to connect to or
 *      an object specifying host and/or port and/or db for the default database to
 *      use on this connection. Any key not supplied will revert to the default.
 * @param {?function(Error)=} opt_errorHandler optional error handler to use for this connection
 * @constructor
 */
rethinkdb.Connection = function(host, opt_errorHandler) {
    if (typeof host === 'undefined') {
        host = {};
    } else if (typeof host === 'string') {
        host = {'host':host};
    }

    this.host_ = host['host'] || this.DEFAULT_HOST;
    this.port_ = host['port'] || this.DEFAULT_PORT;
    this.db_   = host['db']   || this.DEFAULT_DB;

    typeCheck_(this.host_, 'string');
    typeCheck_(this.port_, 'number');
    typeCheck_(this.db_,   'string');

    this.outstandingQueries_ = {};
    this.nextToken_ = 1;

    this.errorHandler_ = opt_errorHandler || null;

    rethinkdb.last_connection_ = this;
};

/**
 * The default host to use for new connections that don't specify a host
 * @constant
 * @type {string}
 */
rethinkdb.Connection.prototype.DEFAULT_HOST = 'localhost';

/**
 * The default port to use for new connections that don't specify a port
 * @type {number}
 */
rethinkdb.Connection.prototype.DEFAULT_PORT = 12346;

/**
 * The default database to use for new connections that don't specify one
 * @constant
 * @type {string}
 */
rethinkdb.Connection.prototype.DEFAULT_DB = 'test';

/**
 * Closes this connection and reopens a new connection to the same host
 * and port.
 * @param {function(...)=} onConnect
 */
rethinkdb.Connection.prototype.reconnect = function(onConnect) {
    this.constructor.call(this, {'host':this.host_,
                                 'port':this.port_,
                                 'db'  :this.db_}, onConnect, this.errorHandler_);
};
goog.exportProperty(rethinkdb.Connection.prototype, 'reconnect',
                    rethinkdb.Connection.prototype.reconnect);

/**
 * Invoke the error handler on this connection.
 * @param {Error} error The origional error thrown
 * @ignore
 */
rethinkdb.Connection.prototype.error_ = function(error) {
    if (this.errorHandler_) {
        this.errorHandler_(error);
    } else {
        // Simply rethrow the error. It'll go uncaught and show up in the developer's console.

        // THIS IS A HACAK, but Slava wants it
        if (this.printErr_) {
            console.log(error.name + ":" + error.message);
        } else {
            throw error;
        }
    }
};

/**
 * Set the error handler to use for this connection. Will be called with any error
 * returned by the server.
 * @param {function(Error)} handler The error handler to use
 */
rethinkdb.Connection.prototype.setErrorHandler = function(handler) {
    this.errorHandler_ = handler;
};
goog.exportProperty(rethinkdb.Connection.prototype, 'setErrorHandler',
                    rethinkdb.Connection.prototype.setErrorHandler);

/**
 * Send the protobuf over the underlying connection. Implemented by subclasses.
 * @param {ArrayBuffer} data The protocol buffer to send
 * @private
 */
rethinkdb.Connection.prototype.send_ = goog.abstractMethod;

/**
 * Close the underlying connection. Implemented by subclass.
 * @private
 */
rethinkdb.Connection.prototype.close = goog.abstractMethod;

/**
 * Construct and run the given query, used by public run and iter methods
 * @param {rethinkdb.Query} expr The expression to run
 * @param {boolean} iterate Iterate callback over results
 * @param {function(...)} opt_callback Callback to which results are returned
 * @param {function(...)=} opt_doneCallback called when stream has ended.
 *  Only valid if iterate = true
 * @private
 */
rethinkdb.Connection.prototype.run_ = function(expr, iterate, opt_callback, opt_doneCallback) {
    var query = expr.buildQuery();

    // Assign a token
    query.setToken((this.nextToken_++).toString());

    this.outstandingQueries_[query.getToken()] = {
        callback: (opt_callback || null),
        query: expr, // Save origional ast for backtrace reconstructions
        iterate : iterate,
        done: (opt_doneCallback || null),
        partial : []
    };

    this.sendProtoBuf_(query);
};

/**
 * Serializes and sends protocol buffer object on this connection.
 * @param {goog.proto2.Message} pbObj
 * @private
 */
rethinkdb.Connection.prototype.sendProtoBuf_ = function(pbObj) {
    var serializer = new goog.proto2.WireFormatSerializer();
    var data = serializer.serialize(pbObj);

    // RDB protocol requires a 4 byte little endian length field at the head of the request
    var length = data.byteLength;
    var finalArray = new Uint8Array(length + 4);
    (new DataView(finalArray.buffer)).setInt32(0, length, true);
    finalArray.set(data, 4);

    this.send_(finalArray.buffer);
};

/**
 * Evaluates the given ReQL expression on the server and invokes
 * callback with the result.
 * @param {rethinkdb.Query} expr The expression to run.
 * @param {function(...)} opt_callback Function to invoke with response.
 */
rethinkdb.Connection.prototype.run = function(expr, opt_callback) {
    // THIS IS A HACAK, but Slava wants it
    this.printErr_ = false;

    argCheck_(arguments, 1);
    typeCheck_(expr, rethinkdb.Query);
    typeCheck_(opt_callback, 'function');
    this.run_(expr, false, opt_callback);
};
goog.exportProperty(rethinkdb.Connection.prototype, 'run',
                    rethinkdb.Connection.prototype.run);

/**
 * Version of run that prints the result rather than taking a callback
 * @param {rethinkdb.Query} expr The expression to run.
 */
rethinkdb.Connection.prototype.runp = function(expr) {
    // THIS IS A HACAK, but Slava wants it
    this.printErr_ = true;

    this.run_(expr, false, function(val) {
        console.log(val);
    });
};
goog.exportProperty(rethinkdb.Connection.prototype, 'runp',
                    rethinkdb.Connection.prototype.runp);

/**
 * Evaluates the given ReQL expression on the server and invokes
 * callback with each element of the result. The main advantage of using iter
 * over run is that results are lazily loaded as they are returned from the
 * server. Use anytime the result is expected to be very large.
 * @param {rethinkdb.Query} expr The expression to run.
 * @param {function(...)} opt_callback Function to invoke with response.
 * @param {function(...)=} opt_doneCallback Function to invoke when stream has ended.
 */
rethinkdb.Connection.prototype.iter = function(expr, opt_callback, opt_doneCallback) {
    // THIS IS A HACAK, but Slava wants it
    this.printErr_ = false;

    argCheck_(arguments, 1);
    typeCheck_(expr, rethinkdb.Query);
    typeCheck_(opt_callback, 'function');
    this.run_(expr, true, opt_callback, opt_doneCallback);
};
goog.exportProperty(rethinkdb.Connection.prototype, 'iter',
                    rethinkdb.Connection.prototype.iter);

/**
 * Called by subclass when data is received on the underlying connection.
 * @param {Uint8Array} data Received data from the connection
 * @private
 */
rethinkdb.Connection.prototype.recv_ = function(data) {
    goog.asserts.assert(data.length >= 4);
    var msgLength = (new DataView(data.buffer)).getUint32(0, true);
    if (msgLength !== (data.length - 4)) {
        this.error_(new rethinkdb.errors.ClientError("RDB response corrupted, length inaccurate"));
    }

    var serializer = new goog.proto2.WireFormatSerializer();
    var response = new Response();

    try {
        serializer.deserializeTo(response, new Uint8Array(data.buffer, 4));
    } catch(err) {
        this.error_(new rethinkdb.errors.ClientError("response deserialization failed"));
    }

    var responseStatus = response.getStatusCode();
    var request = this.outstandingQueries_[response.getToken()];
    if (request) {

        switch(responseStatus) {
        case Response.StatusCode.BROKEN_CLIENT:
            this.error_(new rethinkdb.errors.BrokenClient(response.getErrorMessage()));
            break;
        case Response.StatusCode.RUNTIME_ERROR:
            this.error_(new rethinkdb.errors.RuntimeError(formatServerError_(response, request.query)));
            break;
        case Response.StatusCode.BAD_QUERY:
            this.error_(new rethinkdb.errors.BadQuery(response.getErrorMessage()));
            break;
        case Response.StatusCode.SUCCESS_EMPTY:
            delete this.outstandingQueries_[response.getToken()]
            if (request.callback) request.callback();
            break;
        case Response.StatusCode.SUCCESS_STREAM:
            delete this.outstandingQueries_[response.getToken()]
            var results = response.responseArray().map(JSON.parse);
            if (request.callback) {
                if (request.iterate) {
                    results.forEach(request.callback);
                } else {
                    request.callback(request.partial.concat(results));
                }
            }

            if (request.done && request.iterate) request.done();
            break;
        case Response.StatusCode.SUCCESS_JSON:
            delete this.outstandingQueries_[response.getToken()]
            var result = JSON.parse(response.getResponse(0));
            if (request.iterate) {
                if (result.forEach && request.callback) {
                    result.forEach(request.callback);
                } else if (request.callback) {
                    request.callback(result);
                }
                if (request.done) {
                    request.done();
                }
            } else if (request.callback) {
                request.callback(result);
            }
            break;
        case Response.StatusCode.SUCCESS_PARTIAL:
            var cont = new Query();
            cont.setType(Query.QueryType.CONTINUE);
            cont.setToken(response.getTokenOrDefault());
            this.sendProtoBuf_(cont);

            var results = response.responseArray().map(JSON.parse);
            if (request.callback) {
                if (request.iterate) {
                    results.forEach(request.callback);
                } else {
                    // Save results for eventual callback call
                    request.partial = request.partial.concat(results);
                }
            }
            break;
        default:
            this.error_(new rethinkdb.errors.ClientError("unknown response status code"));
            break;
        }
    } // else no matching request
};

/**
 * Set the default db to use for this connection
 * @param {string} dbName
 */
rethinkdb.Connection.prototype.use = function(dbName) {
    argCheck_(arguments, 1);
    typeCheck_(dbName, 'string');
	this.db_ = dbName;
};

/**
 * Return the current default db for this connection
 * @return {string}
 */
rethinkdb.Connection.prototype.getDefaultDb = function() {
    return this.db_;
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015, Groupon, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of GROUPON nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

const assert = require('assertive');
const { isEmpty } = require('lodash');

function getTotalScore(success, failure) {
  const successCount = Object.keys(success).length;
  const errorCount = Object.keys(failure).length;

  return (successCount * 100) / (successCount + errorCount);
}

const lhConfig = {
  passes: [
    {
      passName: 'accessibilityPass',
      gatherers: ['accessibility'],
    },
  ],
  audits: [
    'accessibility/accesskeys',
    'accessibility/aria-allowed-attr',
    'accessibility/aria-required-attr',
    'accessibility/aria-required-children',
    'accessibility/aria-required-parent',
    'accessibility/aria-roles',
    'accessibility/aria-valid-attr-value',
    'accessibility/aria-valid-attr',
    'accessibility/audio-caption',
    'accessibility/button-name',
    'accessibility/bypass',
    'accessibility/color-contrast',
    'accessibility/definition-list',
    'accessibility/dlitem',
    'accessibility/document-title',
    'accessibility/duplicate-id',
    'accessibility/frame-title',
    'accessibility/html-has-lang',
    'accessibility/html-lang-valid',
    'accessibility/image-alt',
    'accessibility/input-image-alt',
    'accessibility/label',
    'accessibility/layout-table',
    'accessibility/link-name',
    'accessibility/list',
    'accessibility/listitem',
    'accessibility/meta-refresh',
    'accessibility/meta-viewport',
    'accessibility/object-alt',
    'accessibility/tabindex',
    'accessibility/td-headers-attr',
    'accessibility/th-has-data-cells',
    'accessibility/valid-lang',
    'accessibility/video-caption',
    'accessibility/video-description',
  ],
};

const parseLhResult = function(results) {
  if (!results || !results.lhr || !results.lhr.audits) {
    return null;
  }

  const audits = results.lhr.audits;

  const errorsObj = {};
  const notApplicableObj = {};
  const successObj = {};

  for (const key in audits) {
    const audit = audits[key];
    const description = audit.description;
    const details = audit.details;
    const id = audit.id;
    const score = audit.score;
    const scoreDisplayMode = audit.scoreDisplayMode;

    const auditInfo = { description, details };

    if (score) {
      successObj[id] = auditInfo;
    } else if (scoreDisplayMode === 'not-applicable') {
      notApplicableObj[id] = auditInfo;
    } else if (scoreDisplayMode !== 'manual') {
      const snippets = [];
      if (!isEmpty(details.items)) {
        details.items.forEach(item => snippets.push(item.node.snippet || ''));
      }

      errorsObj[id] = { description, snippets };
    }
  }

  const score = getTotalScore(successObj, errorsObj);

  return {
    audits,
    score,
    isSuccess(cutoff) {
      return score >= cutoff;
    },
    success(type) {
      return type ? successObj[type] : successObj;
    },
    errors(type) {
      return type ? errorsObj[type] : errorsObj;
    },
    errorString() {
      const errorList = [];

      for (const id in errorsObj) {
        const description = errorsObj[id].description;
        const snippets = errorsObj[id].snippets;
        errorList.push(`${description}:\n\t${snippets.join('\n\t')}`);
      }

      return errorList.join('\n\n');
    },
  };
};

exports.assertLighthouseScore = function assertLighthouseScore(
  score,
  flags,
  config
) {
  const assertScore = function aScore(res) {
    assert.expect(parseLhResult(res).isSuccess(score));
  };

  return this.getLighthouseData(flags, config || lhConfig).then(assertScore);
};

exports.runLighthouseAudit = function runLighthouseAudit(flags, config) {
  const assertScore = function aScore(res) {
    assert.expect(parseLhResult(res).isSuccess(score));
  };

  return this.getLighthouseData(flags, config || lhConfig).then(parseLhResult);
};
<commit_msg>chore: test fixes<commit_after>/*
 * Copyright (c) 2015, Groupon, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of GROUPON nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

const assert = require('assertive');
const isEmpty = require('lodash/isEmpty');

function getTotalScore(success, failure) {
  const successCount = Object.keys(success).length;
  const errorCount = Object.keys(failure).length;

  return (successCount * 100) / (successCount + errorCount);
}

const lhConfig = {
  passes: [
    {
      passName: 'accessibilityPass',
      gatherers: ['accessibility'],
    },
  ],
  audits: [
    'accessibility/accesskeys',
    'accessibility/aria-allowed-attr',
    'accessibility/aria-required-attr',
    'accessibility/aria-required-children',
    'accessibility/aria-required-parent',
    'accessibility/aria-roles',
    'accessibility/aria-valid-attr-value',
    'accessibility/aria-valid-attr',
    'accessibility/audio-caption',
    'accessibility/button-name',
    'accessibility/bypass',
    'accessibility/color-contrast',
    'accessibility/definition-list',
    'accessibility/dlitem',
    'accessibility/document-title',
    'accessibility/duplicate-id',
    'accessibility/frame-title',
    'accessibility/html-has-lang',
    'accessibility/html-lang-valid',
    'accessibility/image-alt',
    'accessibility/input-image-alt',
    'accessibility/label',
    'accessibility/layout-table',
    'accessibility/link-name',
    'accessibility/list',
    'accessibility/listitem',
    'accessibility/meta-refresh',
    'accessibility/meta-viewport',
    'accessibility/object-alt',
    'accessibility/tabindex',
    'accessibility/td-headers-attr',
    'accessibility/th-has-data-cells',
    'accessibility/valid-lang',
    'accessibility/video-caption',
    'accessibility/video-description',
  ],
};

const parseLhResult = function(results) {
  if (!results || !results.lhr || !results.lhr.audits) {
    return null;
  }

  const audits = results.lhr.audits;

  const errorsObj = {};
  const notApplicableObj = {};
  const successObj = {};

  for (const key in audits) {
    const audit = audits[key];
    const description = audit.description;
    const details = audit.details;
    const id = audit.id;
    const score = audit.score;
    const scoreDisplayMode = audit.scoreDisplayMode;

    const auditInfo = { description, details };

    if (score) {
      successObj[id] = auditInfo;
    } else if (scoreDisplayMode === 'not-applicable') {
      notApplicableObj[id] = auditInfo;
    } else if (scoreDisplayMode !== 'manual') {
      const snippets = [];
      if (!isEmpty(details.items)) {
        details.items.forEach(item => snippets.push(item.node.snippet || ''));
      }

      errorsObj[id] = { description, snippets };
    }
  }

  const score = getTotalScore(successObj, errorsObj);

  return {
    audits,
    score,
    isSuccess(cutoff) {
      return score >= cutoff;
    },
    success(type) {
      return type ? successObj[type] : successObj;
    },
    errors(type) {
      return type ? errorsObj[type] : errorsObj;
    },
    errorString() {
      const errorList = [];

      for (const id in errorsObj) {
        const description = errorsObj[id].description;
        const snippets = errorsObj[id].snippets;
        errorList.push(`${description}:\n\t${snippets.join('\n\t')}`);
      }

      return errorList.join('\n\n');
    },
  };
};

exports.assertLighthouseScore = function assertLighthouseScore(
  score,
  flags,
  config
) {
  const assertScore = function aScore(res) {
    assert.expect(parseLhResult(res).isSuccess(score));
  };

  return this.getLighthouseData(flags, config || lhConfig).then(assertScore);
};

exports.runLighthouseAudit = function runLighthouseAudit(flags, config) {
  return this.getLighthouseData(flags, config || lhConfig).then(parseLhResult);
};
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var mongoose = require('mongoose');
var httpMocks = require('node-mocks-http');

var jsonapify = require('../../');
var Resource = jsonapify.Resource;
var create = jsonapify.middleware.create;
var InvalidFieldValue = jsonapify.errors.InvalidFieldValue;

describe('create', function() {
	var model, resource, res;
	before(function(done) {
		mongoose.connect('mongodb://localhost/test', function(err) {
			if (err) return done(err);
			model = mongoose.model('CreateTest', new mongoose.Schema);
			done();
		});
	});

	beforeEach(function() {
		resource = new Resource(model, { type: 'test' });
		res = httpMocks.createResponse();
	});

	afterEach(function(done) {
		mongoose.connection.db.dropDatabase(done);
	});

	after(function(done) {
		mongoose.disconnect(done);
	});

	it('creates resource and sends back resource data', function(done) {
		var req = httpMocks.createRequest({ body: { data: { type: 'test' }}});
		create(resource)(req, res, function(err) {
			if (err) return done(err);
			model.find(function(err, results) {
				if (err) return done(err);
				expect(results).to.have.length(1);
				done();
			});
		});
	});

	it('sends an error if wrong type', function(done) {
		var req = httpMocks.createRequest({ body: { data: { type: 'invalid' }}});
		create(resource)(req, res, function(err) {
			expect(err).to.be.an.instanceof(InvalidFieldValue);
			model.find(function(err, results) {
				if (err) return done(err);
				expect(results).to.be.empty;
				done();
			});
		});
	});
});
<commit_msg>Minor changes<commit_after>var expect = require('chai').expect;
var mongoose = require('mongoose');
var httpMocks = require('node-mocks-http');

var jsonapify = require('../../');
var Resource = jsonapify.Resource;
var create = jsonapify.middleware.create;
var InvalidFieldValue = jsonapify.errors.InvalidFieldValue;

describe('create', function() {
	var model, resource, res;
	before(function(done) {
		mongoose.connect('mongodb://localhost/test', function(err) {
			if (err) return done(err);
			model = mongoose.model('CreateTest', new mongoose.Schema);
			done();
		});
	});

	beforeEach(function() {
		resource = new Resource(model, { type: 'test' });
		res = httpMocks.createResponse();
	});

	afterEach(function(done) {
		mongoose.connection.db.dropDatabase(done);
	});

	after(function(done) {
		mongoose.disconnect(done);
	});

	it('creates resource and sends back resource data', function(done) {
		var req = httpMocks.createRequest({ body: { data: { type: 'test' }}});
		create(resource)(req, res, function(err) {
			if (err) return done(err);
			var resdata = res._getData();
			resdata = JSON.parse(resdata);
			expect(resdata).to.have.deep.property('data.type', 'test');
			model.count(function(err, count) {
				if (err) return done(err);
				expect(count).to.equal(1);
				done();
			});
		});
	});

	it('sends an error if trying to create resource with wrong type', function(done) {
		var req = httpMocks.createRequest({ body: { data: { type: 'invalid' }}});
		create(resource)(req, res, function(err) {
			expect(err).to.be.an.instanceof(InvalidFieldValue);
			model.find(function(err, results) {
				if (err) return done(err);
				expect(results).to.be.empty;
				done();
			});
		});
	});
});
<|endoftext|>


<commit_before>var calculatorWidget = angular.module("homepageApp");

calculatorWidget.factory("calcFactory", function() {

    var calcFac = {};

    calcFac.blahTest = function() {
        alert("hello");
        console.log("Hello");
    }

    calcFac.loadButtons = function() {

        var keys = document.querySelectorAll('#calculator span');

        var countParanthesis = 0;

        // Add onclick event to all the keys and perform operations
        for(var i = 0; i < keys.length; i++) {
            keys[i].onclick = function (e) {
                // Get the input and button values
                var screenValue = document.querySelector('.screen');
                var buttonPressed = this.innerHTML;
                var lastChar = screenValue.innerHTML[tempLength - 1];

                //erase everthing
                if (buttonPressed == 'C') {
                    screenValue.innerHTML = ''; //clear value
                }
                // If eval key is pressed, calculate and display the result
                else if (buttonPressed == '=') {
                    screenValue.innerHTML = evaluateString(screenValue.innerHTML);
                }
                else if (buttonPressed == '(') {
                    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                }
                else if (buttonPressed == 'sin' || buttonPressed == 'cos' || buttonPressed == 'tan' || buttonPressed == 'log') {
                    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                    screenValue.innerHTML += '(';
                }
                else if (buttonPressed == ')') {
                    if (countParanthesis == 0) {
                        // alert("no matching paranthesis (");
                    }
                    else {
                        screenValue.innerHTML += buttonPressed;
                        countParanthesis -= 1;
                    }
                }
                //validate the operator operations(no two operators )
                //else if(isOperator(buttonPressed)) {
                //    screenValue.innerHTML = validateOperator(screenValue.innerHTML, buttonPressed);
                //}
                //validate decimal
                else if (buttonPressed == '.') {
                    screenValue.innerHTML = inputDecimal(screenValue.innerHTML);
                }
                else if (buttonPressed == 'del') {
                    var tempLength = screenValue.innerHTML.length;
                    lastChar = screenValue.innerHTML[tempLength - 1];
                    if (isNumber(lastChar) || isOperator(lastChar) || lastChar == '.') {
                        screenValue.innerHTML = screenValue.innerHTML.substring(0, tempLength - 1);
                    }
                    else if (lastChar == ')') {
                        countParanthesis += 1;
                        screenValue.innerHTML = screenValue.innerHTML.substring(0, tempLength - 1);
                    }
                    else if (lastChar == '(') {
                        screenValue.innerHTML = screenValue.innerHTML.substring(0, tempLength - 1);
                        lastChar = screenValue.innerHTML[screenValue.innerHTML.length - 1];
                        while (lastChar == 's' || lastChar == 'i' || lastChar == 'o' || lastChar == 'n' || lastChar == 't' || lastChar == 'a' || lastChar == 'c'
                        || lastChar == 'l' || lastChar == 'g') {
                            screenValue.innerHTML = screenValue.innerHTML.substring(0, screenValue.innerHTML.length - 1);
                            lastChar = screenValue.innerHTML[screenValue.innerHTML.length - 1];
                            if (screenValue.innerHTML.length <= 0) {
                                break;
                            }
                        }
                    }
                }
                else {
                    screenValue.innerHTML += buttonPressed;
                }

                // prevent page jumps
                e.preventDefault();
            }

            /////////////////////////////*************************************/////////////////////////////////////
            function clearScreen(){
                var screenValue = document.querySelector('.screen');
                screenValue.innerHTML = "";
            }

            //evaluate the given string and return the value
            function evaluateString(input) {
                var equation = input;
                var lastChar = equation[equation.length - 1];
                equation = equation.replace(/x/g, '*').replace(/÷/g, '/');

                var operatorList = getOperatorList();
                if(isOperator(lastChar) || lastChar == '.')
                    equation = equation.replace(/.$/, '');
                var result =  math.eval(equation);
                return result;
            }
            function validateOperator(input,operator){
                var lastChar = input[input.length - 1];

                var result = input;
                if(input != '' &&  !isOperator(lastChar)) {//if last is not operator
                    result += operator;
                }
                else if(input == '' && operator == '-'){//if negative
                    result +=operator;
                }
                else if(isOperator(lastChar)){//if last is operator
                    result = result.replace(/.$/, operator);
                }

                return result;
            }

            function inputDecimal(input){
                result = input;
                var lastChar = input[input.length - 1];
                if(isNumber(lastChar) == true || lastChar == '' || lastChar == ' '){
                    result += '.';
                }

                return result;
            }

            function getOperatorList(){
                return ['+', '-', 'x', '/','^','÷','*','!'];
            }

            function isNumber(input){
                var numberList = ['0','1','2','3','4','5','6','7','8','9'];
                for(var i = 0; i < numberList.length; i++)
                {
                    if(input == numberList[i])
                        return true;
                }
                return false;
            }

            function isOperator(input){
                if(input.length > 1)
                    return false;
                if(getOperatorList().indexOf(input) > -1){
                    return true;
                }
                return false;
            }
        }
    }

    return calcFac;
});

calculatorWidget.controller("calcController", ["$scope", "calcFactory",
    function($scope, calcFactory) {

        addEventListener('load',calcFactory.loadButtons,false);
        $scope.loadButtons = calcFactory.loadButtons();

        $scope.swap = function(one, two) {

            one = document.getElementById(one);
            two = document.getElementById(two);

            // Displaying the settings
            if (one.style.display == 'block') {
                two.style.display = 'block';
                one.style.display = 'none';
            }
            else { // Displaying the calculator
                one.style.display = 'block';
                two.style.display = 'none';
            }
        }

        $scope.changeCalculatorColor = function(color) {
            var calculator = document.getElementById('calculator');
            calculator.style.background = color;
        }

    }]);
<commit_msg>refactored calculator<commit_after>var calculatorWidget = angular.module("homepageApp");

calculatorWidget.factory("calcFactory", function() {

    var calcFac = {};

    calcFac.blahTest = function() {
        alert("hello");
        console.log("Hello");
    }

    calcFac.loadButtons = function() {

        var keys = document.querySelectorAll('#calculator span');

        var countParanthesis = 0;

        // Add onclick event to all the keys and perform operations
        for(var i = 0; i < keys.length; i++) {
            keys[i].onclick = function (e) {
                // Get the input and button values
                var screenValue = document.querySelector('.screen');
                var buttonPressed = this.innerHTML;
                var lastChar = screenValue.innerHTML[tempLength - 1];

                //erase everthing
                switch (buttonPressed){
                case 'C':
                    screenValue.innerHTML = ''; //clear value
                	break;
                // If eval key is pressed, calculate and display the result
                case '=':
                    screenValue.innerHTML = evaluateString(screenValue.innerHTML);
                	break;
                case '(':
                    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                	break;
                case 'sin':
                    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                    screenValue.innerHTML += '(';
                	break;
                case 'tan':
                    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                    screenValue.innerHTML += '(';
                	break;
                case 'cos':
                    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                    screenValue.innerHTML += '(';
                	break;
				case 'log':
				    countParanthesis += 1;
                    screenValue.innerHTML += buttonPressed;
                    screenValue.innerHTML += '(';
                	break;
                case ')':
                    if (countParanthesis == 0) {
                        // alert("no matching paranthesis (");
                    }
                    else {
                        screenValue.innerHTML += buttonPressed;
                        countParanthesis -= 1;
                    }
                    break;
				case '.':
                    screenValue.innerHTML = inputDecimal(screenValue.innerHTML);
                	break;
                case 'del':
                    var tempLength = screenValue.innerHTML.length;
                    lastChar = screenValue.innerHTML[tempLength - 1];
                    if (isNumber(lastChar) || isOperator(lastChar) || lastChar == '.') {
                        screenValue.innerHTML = screenValue.innerHTML.substring(0, tempLength - 1);
                    }
                    else if (lastChar == ')') {
                        countParanthesis += 1;
                        screenValue.innerHTML = screenValue.innerHTML.substring(0, tempLength - 1);
                    }
                    else if (lastChar == '(') {
                        screenValue.innerHTML = screenValue.innerHTML.substring(0, tempLength - 1);
                        lastChar = screenValue.innerHTML[screenValue.innerHTML.length - 1];
                        while (lastChar == 's' || lastChar == 'i' || lastChar == 'o' || lastChar == 'n' || lastChar == 't' || lastChar == 'a' || lastChar == 'c'
                        || lastChar == 'l' || lastChar == 'g') {
                            screenValue.innerHTML = screenValue.innerHTML.substring(0, screenValue.innerHTML.length - 1);
                            lastChar = screenValue.innerHTML[screenValue.innerHTML.length - 1];
                            if (screenValue.innerHTML.length <= 0) {
                                break;
                            }
                        }
                    }
                    break;
                default:
                    screenValue.innerHTML += buttonPressed;

				}
                // prevent page jumps
                e.preventDefault();
            }

            /////////////////////////////*************************************/////////////////////////////////////
            function clearScreen(){
                var screenValue = document.querySelector('.screen');
                screenValue.innerHTML = "";
            }

            //evaluate the given string and return the value
            function evaluateString(input) {
                var equation = input;
                var lastChar = equation[equation.length - 1];
                equation = equation.replace(/x/g, '*').replace(/÷/g, '/');

                var operatorList = getOperatorList();
                if(isOperator(lastChar) || lastChar == '.')
                    equation = equation.replace(/.$/, '');
                var result =  math.eval(equation);
                return result;
            }
            function validateOperator(input,operator){
                var lastChar = input[input.length - 1];

                var result = input;
                if(input != '' &&  !isOperator(lastChar)) {//if last is not operator
                    result += operator;
                }
                else if(input == '' && operator == '-'){//if negative
                    result +=operator;
                }
                else if(isOperator(lastChar)){//if last is operator
                    result = result.replace(/.$/, operator);
                }

                return result;
            }

            function inputDecimal(input){
                result = input;
                var lastChar = input[input.length - 1];
                if(isNumber(lastChar) == true || lastChar == '' || lastChar == ' '){
                    result += '.';
                }

                return result;
            }

            function getOperatorList(){
                return ['+', '-', 'x', '/','^','÷','*','!'];
            }

            function isNumber(input){
                var numberList = ['0','1','2','3','4','5','6','7','8','9'];
                for(var i = 0; i < numberList.length; i++)
                {
                    if(input == numberList[i])
                        return true;
                }
                return false;
            }

            function isOperator(input){
                if(input.length > 1)
                    return false;
                if(getOperatorList().indexOf(input) > -1){
                    return true;
                }
                return false;
            }
        }
    }

    return calcFac;
});

calculatorWidget.controller("calcController", ["$scope", "calcFactory",
    function($scope, calcFactory) {

        addEventListener('load',calcFactory.loadButtons,false);
        $scope.loadButtons = calcFactory.loadButtons();

        $scope.swap = function(one, two) {

            one = document.getElementById(one);
            two = document.getElementById(two);

            // Displaying the settings
            if (one.style.display == 'block') {
                two.style.display = 'block';
                one.style.display = 'none';
            }
            else { // Displaying the calculator
                one.style.display = 'block';
                two.style.display = 'none';
            }
        }

        $scope.changeCalculatorColor = function(color) {
            var calculator = document.getElementById('calculator');
            calculator.style.background = color;
        }

    }]);
<|endoftext|>


<commit_before>/**
 * @module nock/intercepts
 */

var RequestOverrider = require('./request_overrider'),
    mixin            = require('./mixin'),
    path             = require('path'),
    url              = require('url'),
    inherits         = require('util').inherits,
    EventEmitter     = require('events').EventEmitter,
    http             = require('http'),
    parse            = require('url').parse,
    ClientRequest    = http.ClientRequest

/**
 * @name NetConnectNotAllowedError
 * @private
 * @desc Error trying to make a connection when disabled external access.
 * @class
 * @example
 * nock.disableNetConnect();
 * http.get('http://zombo.com');
 * // throw NetConnectNotAllowedError
 */
function NetConnectNotAllowedError(host) {
  this.message = 'Nock: Not allow net connect for "' + host + '"';
  this.name    = 'NetConnectNotAllowedError';
}

var allInterceptors = {},
    allowNetConnect = /.*/;

/**
 * Enabled real request.
 * @public
 * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match
 * @example
 * // Enables all real requests
 * nock.enableNetConnect();
 * @example
 * // Enables real requests for url that matches google
 * nock.enableNetConnect('google');
 * @example
 * // Enables real requests for url that matches google and amazon
 * nock.enableNetConnect(/(google|amazon)/);
 */
function enableNetConnect(matcher) {
  if (typeof matcher === 'string') {
    allowNetConnect = new RegExp(matcher);
  } else if (typeof matcher === 'object' && typeof matcher.test === 'function') {
    allowNetConnect = matcher;
  } else {
    allowNetConnect = /.*/;
  }
}

function isEnabledForNetConnect(options) {
  normalizeForRequest(options);

  return allowNetConnect && allowNetConnect.test(options.host);
}

/**
 * Disable all real requests.
 * @public
 * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match
 * @example
 * nock.disableNetConnect();
*/
function disableNetConnect() {
  allowNetConnect = false;
}

function isOn() {
  return !isOff();
}

function isOff() {
  return process.env.NOCK_OFF === 'true';
}

function add(key, interceptor, scope) {
  if (! allInterceptors.hasOwnProperty(key)) {
    allInterceptors[key] = [];
  }
  interceptor.__nock_scope = scope;
  allInterceptors[key].push(interceptor);
}

function remove(interceptor) {
  if (interceptor.__nock_scope.shouldPersist()) return;

  if (interceptor.counter > 1) {
    interceptor.counter -= 1;
    return;
  }

  var key          = interceptor._key.split(' '),
      u            = url.parse(key[1]),
      hostKey      = u.protocol + '//' + u.host,
      interceptors = allInterceptors[hostKey],
      interceptor,
      thisInterceptor;

  if (interceptors) {
    for(var i = 0; i < interceptors.length; i++) {
      thisInterceptor = interceptors[i];
      if (thisInterceptor === interceptor) {
        interceptors.splice(i, 1);
        break;
      }
    }

  }
}

function removeAll() {
  allInterceptors = {};
}

function normalizeForRequest(options) {
  options.proto = options.proto || 'http';
  options.port = options.port || ((options.proto === 'http') ? 80 : 443);
  if (options.host) {
    options.hostname = options.hostname || options.host.split(':')[0];
  }
  options.host = (options.hostname || 'localhost') + ':' + options.port;

  return options;
}

function interceptorsFor(options) {
  var basePath;

  normalizeForRequest(options);

  basePath = options.proto + '://' + options.host;

  return allInterceptors[basePath] || [];
}

function activate() n{
  // ----- Extending http.ClientRequest

  function OverridenClientRequest(options, cb) {
    var interceptors = interceptorsFor(options);

    if (interceptors.length) {
      var overrider = RequestOverrider(this, options, interceptors, remove, cb);
      for(var propName in overrider) {
        if (overrider.hasOwnProperty(propName)) {
          this[propName] = overrider[propName];
        }
      }
    } else {
      ClientRequest.apply(this, arguments);
    }

  }
  inherits(OverridenClientRequest, ClientRequest);

  http.ClientRequest = OverridenClientRequest;

  // ----- Overriding http.request and https.request:

  [ 'http', 'https'].forEach(
    function(proto) {

      var moduleName = proto, // 1 to 1 match of protocol and module is fortunate :)
          module = require(moduleName),
          oldRequest = module.request;

      module.request = function(options, callback) {

        var interceptors,
            req,
            res;

        if (typeof options === 'string') { options = parse(options); }
        options.proto = proto;
        interceptors = interceptorsFor(options);

        if (isOn() && interceptors.length) {

          var matches = false,
              allowUnmocked = false;

          interceptors.forEach(function(interceptor) {
            if (! allowUnmocked && interceptor.options.allowUnmocked) { allowUnmocked = true; }
            if (interceptor.matchIndependentOfBody(options)) { matches = true; }
          });

          if (! matches && allowUnmocked) {
            return oldRequest.apply(module, arguments);
          }

          req = new OverridenClientRequest(options);

          res = RequestOverrider(req, options, interceptors, remove);
          if (callback) {
            res.on('response', callback);
          }
          return req;
        } else {
          if (isOff() || isEnabledForNetConnect(options)) {
            return oldRequest.apply(module, arguments);
          } else {
            throw new NetConnectNotAllowedError(options.host);
          }
        }
      };
    }
  );
}

activate();

module.exports = add;
module.exports.removeAll = removeAll;
module.exports.isOn = isOn;
module.exports.activate = activate;
module.exports.enableNetConnect = enableNetConnect;
module.exports.disableNetConnect = disableNetConnect;
<commit_msg>Update intercept.js<commit_after>/**
 * @module nock/intercepts
 */

var RequestOverrider = require('./request_overrider'),
    mixin            = require('./mixin'),
    path             = require('path'),
    url              = require('url'),
    inherits         = require('util').inherits,
    EventEmitter     = require('events').EventEmitter,
    http             = require('http'),
    parse            = require('url').parse,
    ClientRequest    = http.ClientRequest

/**
 * @name NetConnectNotAllowedError
 * @private
 * @desc Error trying to make a connection when disabled external access.
 * @class
 * @example
 * nock.disableNetConnect();
 * http.get('http://zombo.com');
 * // throw NetConnectNotAllowedError
 */
function NetConnectNotAllowedError(host) {
  this.message = 'Nock: Not allow net connect for "' + host + '"';
  this.name    = 'NetConnectNotAllowedError';
}

var allInterceptors = {},
    allowNetConnect = /.*/;

/**
 * Enabled real request.
 * @public
 * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match
 * @example
 * // Enables all real requests
 * nock.enableNetConnect();
 * @example
 * // Enables real requests for url that matches google
 * nock.enableNetConnect('google');
 * @example
 * // Enables real requests for url that matches google and amazon
 * nock.enableNetConnect(/(google|amazon)/);
 */
function enableNetConnect(matcher) {
  if (typeof matcher === 'string') {
    allowNetConnect = new RegExp(matcher);
  } else if (typeof matcher === 'object' && typeof matcher.test === 'function') {
    allowNetConnect = matcher;
  } else {
    allowNetConnect = /.*/;
  }
}

function isEnabledForNetConnect(options) {
  normalizeForRequest(options);

  return allowNetConnect && allowNetConnect.test(options.host);
}

/**
 * Disable all real requests.
 * @public
 * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match
 * @example
 * nock.disableNetConnect();
*/
function disableNetConnect() {
  allowNetConnect = false;
}

function isOn() {
  return !isOff();
}

function isOff() {
  return process.env.NOCK_OFF === 'true';
}

function add(key, interceptor, scope) {
  if (! allInterceptors.hasOwnProperty(key)) {
    allInterceptors[key] = [];
  }
  interceptor.__nock_scope = scope;
  allInterceptors[key].push(interceptor);
}

function remove(interceptor) {
  if (interceptor.__nock_scope.shouldPersist()) return;

  if (interceptor.counter > 1) {
    interceptor.counter -= 1;
    return;
  }

  var key          = interceptor._key.split(' '),
      u            = url.parse(key[1]),
      hostKey      = u.protocol + '//' + u.host,
      interceptors = allInterceptors[hostKey],
      interceptor,
      thisInterceptor;

  if (interceptors) {
    for(var i = 0; i < interceptors.length; i++) {
      thisInterceptor = interceptors[i];
      if (thisInterceptor === interceptor) {
        interceptors.splice(i, 1);
        break;
      }
    }

  }
}

function removeAll() {
  allInterceptors = {};
}

function normalizeForRequest(options) {
  options.proto = options.proto || 'http';
  options.port = options.port || ((options.proto === 'http') ? 80 : 443);
  if (options.host) {
    options.hostname = options.hostname || options.host.split(':')[0];
  }
  options.host = (options.hostname || 'localhost') + ':' + options.port;

  return options;
}

function interceptorsFor(options) {
  var basePath;

  normalizeForRequest(options);

  basePath = options.proto + '://' + options.host;

  return allInterceptors[basePath] || [];
}

function activate() {
  // ----- Extending http.ClientRequest

  function OverridenClientRequest(options, cb) {
    var interceptors = interceptorsFor(options);

    if (interceptors.length) {
      var overrider = RequestOverrider(this, options, interceptors, remove, cb);
      for(var propName in overrider) {
        if (overrider.hasOwnProperty(propName)) {
          this[propName] = overrider[propName];
        }
      }
    } else {
      ClientRequest.apply(this, arguments);
    }

  }
  inherits(OverridenClientRequest, ClientRequest);

  http.ClientRequest = OverridenClientRequest;

  // ----- Overriding http.request and https.request:

  [ 'http', 'https'].forEach(
    function(proto) {

      var moduleName = proto, // 1 to 1 match of protocol and module is fortunate :)
          module = require(moduleName),
          oldRequest = module.request;

      module.request = function(options, callback) {

        var interceptors,
            req,
            res;

        if (typeof options === 'string') { options = parse(options); }
        options.proto = proto;
        interceptors = interceptorsFor(options);

        if (isOn() && interceptors.length) {

          var matches = false,
              allowUnmocked = false;

          interceptors.forEach(function(interceptor) {
            if (! allowUnmocked && interceptor.options.allowUnmocked) { allowUnmocked = true; }
            if (interceptor.matchIndependentOfBody(options)) { matches = true; }
          });

          if (! matches && allowUnmocked) {
            return oldRequest.apply(module, arguments);
          }

          req = new OverridenClientRequest(options);

          res = RequestOverrider(req, options, interceptors, remove);
          if (callback) {
            res.on('response', callback);
          }
          return req;
        } else {
          if (isOff() || isEnabledForNetConnect(options)) {
            return oldRequest.apply(module, arguments);
          } else {
            throw new NetConnectNotAllowedError(options.host);
          }
        }
      };
    }
  );
}

activate();

module.exports = add;
module.exports.removeAll = removeAll;
module.exports.isOn = isOn;
module.exports.activate = activate;
module.exports.enableNetConnect = enableNetConnect;
module.exports.disableNetConnect = disableNetConnect;
<|endoftext|>


<commit_before>var router 	    = new require('express').Router()
  , bodyParser  = require('body-parser')
  , $fh         = require('fh-mbaas-api')
	, Cloud 	    = require('./cloud')
  , Hash        = require('./hash')
  , Sec         = require('./sec')
  , Service     = require('./service')
  , Db          = require('./db')
  , Cache       = require('./cache')
;

var parser = bodyParser();

router.use(function(req, res, next){
  var start = Date.now();

  $fh.stats.inc('increment');
  $fh.stats.dec('decrement');
  res.on('header', function(){
    $fh.stats.timing('timing', Date.now() - start);
  });

  if(['POST', 'PUT'].indexOf(req.method) !== -1){
    return parser(req, res, next);
  }
  next();
});

exports.route = function(app){
  app.use('/api/v1', router);
  app.use('/api/v1', Cloud.router);
  app.use('/api/v1', Hash.router);
  app.use('/api/v1', Sec.router);
  app.use('/api/v1', Service.router);
  app.use('/api/v1', Db.router);
  app.use('/api/v1', Cache.router);
  app.use(function(err, req, res, next){
    console.error(err.stack || err);
    res.status(500).send('ಠ_ಠ This is why we can\'t have nice things.');
  });
};<commit_msg>update file /api/v1/index.js<commit_after>var router 	    = new require('express').Router()
  , bodyParser  = require('body-parser')
  , $fh         = require('fh-mbaas-api')
	, Cloud 	    = require('./cloud')
  , Hash        = require('./hash')
  , Sec         = require('./sec')
  , Service     = require('./service')
  , Db          = require('./db')
  , Cache       = require('./cache')
;

var parser = bodyParser();

router.use(function(req, res, next){
  var start = Date.now();

  $fh.stats.inc('inc');
  $fh.stats.dec('dec');
  res.on('header', function(){
    $fh.stats.timing('time', Date.now() - start);
  });

  if(['POST', 'PUT'].indexOf(req.method) !== -1){
    return parser(req, res, next);
  }
  next();
});

exports.route = function(app){
  app.use('/api/v1', router);
  app.use('/api/v1', Cloud.router);
  app.use('/api/v1', Hash.router);
  app.use('/api/v1', Sec.router);
  app.use('/api/v1', Service.router);
  app.use('/api/v1', Db.router);
  app.use('/api/v1', Cache.router);
  app.use(function(err, req, res, next){
    console.error(err.stack || err);
    res.status(500).send('ಠ_ಠ This is why we can\'t have nice things.');
  });
};<|endoftext|>