<commit_before>'use strict'; // eslint-disable-line

const HeadlessError = require('node-phantom-simple/headless_error');

const async = require('async');
const debug = require('debug');
const urijs = require('urijs');

const NanoTimer = require('nanotimer');
const Chance = require('chance');
const JSONStream = require('JSONStream');
const createPhantomPool = require('./createPhantomPool.js');

const step = {
    acquireBrowser: require('./worker/steps/acquireBrowser.js'),
    setPageSettings: require('./worker/steps/setPageSettings.js'),
    createPage: require('./worker/steps/createPage.js'),
    openPage: require('./worker/steps/openPage.js'),
    findLinks: require('./worker/steps/findLinks.js'),
    pageRunners: require('./worker/steps/pageRunners.js'),
};

const logger = {
    debug: debug('crawlkit:debug'),
    info: debug('crawlkit:info'),
    error: debug('crawlkit:error'),
};

const concurrencyKey = Symbol();
const urlKey = Symbol();
const finderKey = Symbol();
const timeoutKey = Symbol();
const runnerKey = Symbol();
const phantomParamsKey = Symbol();
const phantomPageSettingsKey = Symbol();
const followRedirectsKey = Symbol();
const browserCookiesKey = Symbol();
const retriesKey = Symbol();
const redirectFilterKey = Symbol();

/**
* Transforms a {Map} to an {Object} hash.
*
* @private
* @param {Map} map The map to transform
* @return {Object} The transformed key/value hash object.
*/
function transformMapToObject(map) {
    const result = {};
    map.forEach((value, key) => {
        result[key] = value;
    });
    return result;
}

/**
* Gets a finder definition of a {@link CrawlKit} instance.
* @private
* @return {Finder} the finder instance set via {@link CrawlKit#setFinder}.
*/
function getFinder(crawlerInstance) {
    return crawlerInstance[finderKey].finder;
}

function getFinderParameters(crawlerInstance) {
    return crawlerInstance[finderKey].parameters;
}

function getRunners(crawlerInstance) {
    return crawlerInstance[runnerKey];
}

/**
* The protocol a URL without a protocol is written to.
*
* @private
* @type {String}
*/
const defaultAbsoluteTo = 'http://';

/**
* The CrawlKit base class. This is where the magic happens.
*/
class CrawlKit {

    /**
    * Create a CrawlKit instance
    * @constructor
    * @param {String} [url] The start URL. Sets the {@link CrawlKit#url}.
    * @return {CrawlKit} a new CrawlKit instance
    */
    constructor(url) {
        if (url) {
            this.url = url;
        }
        this[runnerKey] = new Map();
        this[finderKey] = {};
        this[browserCookiesKey] = [];
    }

    /**
    * Getter/setter for the timeout in ms for runners and finder functions.
    * The timeout starts fresh for each runner.
    *
    * Values under zero are set to zero.
    *
    * @type {!integer}
    * @default 10000 (10 seconds)
    */
    set timeout(num) {
        this[timeoutKey] = parseInt(num, 10);
    }

    /**
    * @ignore
    */
    get timeout() {
        return Math.max(0, this[timeoutKey] || 10000);
    }

    /**
    * Getter/setter for the concurrency of the crawler.
    * This controls the amount of PhantomJS instances that will be spawned
    * and used to work on found websites. Adapt this to the power of your machine.
    *
    * Values under one are set to one.
    *
    * @type {!integer}
    * @default 1 (No concurrency)
    */
    set concurrency(num) {
        this[concurrencyKey] = parseInt(num, 10);
    }

    /**
    * @ignore
    */
    get concurrency() {
        return Math.max(1, this[concurrencyKey] || 1);
    }

    /**
    * Getter/setter for the start URL of the crawler.
    * This is the URL that will be used as an initial endpoint for the crawler.
    * If the protocol is omitted (e.g. URL starts with //), the URL will be rewritten to http://
    * @type {String}
    */
    set url(str) {
        this[urlKey] = str;
    }

    /**
    * @ignore
    */
    get url() {
        return this[urlKey];
    }

    /**
    * With this method a {@link Finder} instance can be set for the crawler.
    * A finder is used for link discovery on a website. It is run directly after page load
    * and is optional (e.g. if you want to only work on a single page).
    *
    * @param {!Finder} finder The finder instance to use for discovery.
    * @param {...*} [runnableParams] These parameters are passed to the function returned by {@link Finder#getRunnable} at evaluation time.
    */
    setFinder(finder /* parameters... */) {
        if (!finder || typeof finder.getRunnable !== 'function') {
            throw new Error('Not a valid finder instance');
        }

        this[finderKey].finder = finder;
        this[finderKey].parameters = Array.prototype.slice.call(arguments, 1);
    }

    /**
    * Getter/setter for the number of retries when a PhantomJS instance crashes on a page.
    * When a PhantomJS instance crashes whilst crawling a webpage, this instance is shutdown
    * and replaced by a new one. By default the webpage that failed in such a way will be
    * re-queued. This member controls how often that re-queueing happens.
    *
    * Values under zero are set to zero.
    *
    * @type {!integer}
    * @default 3 (try 2 more times after the first failure)
    */
    set retries(n) {
        this[retriesKey] = parseInt(n, 10);
    }

    /**
    * @ignore
    */
    get retries() {
        return Math.max(0, this[retriesKey] || 3);
    }

    /**
    * Allows you to add a runner that is executed on each crawled page.
    * The returned value of the runner is added to the overall result.
    * Runners run sequentially on each webpage in the order they were added.
    * If a runner is crashing PhantomJS more than {@link CrawlKit#retries} times, subsequent {@link Runner}s are not executed.
    *
    * @see For an example see `examples/simple.js`. For an example using parameters, see `examples/advanced.js`.
    * @param {!String} key The runner identificator. This is also used in the result stream/object.
    * @param {!Runner} runner The runner instance to use for discovery.
    * @param {...*} [runnableParams] These parameters are passed to the function returned by {@link Runner#getRunnable} at evaluation time.
    */
    addRunner(key, runner /* args ... */) {
        if (!key) {
            throw new Error('Not a valid runner key');
        }
        if (!runner || typeof runner.getCompanionFiles !== 'function' || typeof runner.getRunnable !== 'function') {
            throw new Error('Not a valid runner instance');
        }

        const parameters = Array.prototype.slice.call(arguments, 2);

        this[runnerKey].set(key, {
            runner,
            parameters,
        });
    }

    /**
    * Getter/setter for the map of parameters to pass to PhantomJS.
    * You can use this for example to ignore SSL errors.
    * For a list of parameters, please refer to the [PhantomJS documentation]{@link http://phantomjs.org/api/command-line.html}.
    *
    * @type {!Object.<String,String>}
    */
    set phantomParameters(params) {
        this[phantomParamsKey] = params;
    }

    /**
    * @ignore
    */
    get phantomParameters() {
        return this[phantomParamsKey] || {};
    }

    /**
    * Getter/setter for the map of settings to pass to an opened page.
    * You can use this for example for Basic Authentication.
    * For a list of options, please refer to the [PhantomJS documentation]{@link http://phantomjs.org/api/webpage/property/settings.html}.
    * Nested settings can just be provided in dot notation as the key, e.g. 'settings.userAgent'.
    *
    * @type {!Object.<String,*>}
    */
    set phantomPageSettings(settings) {
        this[phantomPageSettingsKey] = settings;
    }

    /**
    * @ignore
    */
    get phantomPageSettings() {
        return this[phantomPageSettingsKey] || {};
    }

    /**
    * Getter/setter for whether to follow redirects or not.
    * When following redirects, the original page is not processed.
    *
    * @type {!boolean}
    * @default false
    */
    set followRedirects(value) {
        this[followRedirectsKey] = !!value;
    }

    /**
    * @ignore
    */
    get followRedirects() {
        return this[followRedirectsKey] || false;
    }

    /**
    * Getter/setter for the cookies to set within PhantomJS.
    * Each entry is supposed to be an object [following the PhantomJS spec]{@link http://phantomjs.org/api/webpage/method/add-cookie.html}.
    *
    * @type {!Array.<Object>}
    */
    set browserCookies(cookies) {
        if (!(cookies instanceof Array)) {
            throw new Error('Not properly munchable');
        }
        this[browserCookiesKey] = cookies;
    }

    /**
    * @ignore
    */
    get browserCookies() {
        return this[browserCookiesKey];
    }

    /**
    * Getter/setter for the filter that is applied to redirected URLs.
    * With this filter you can prevent the redirect or rewrite it.
    * The filter callback gets two arguments. The first one is the target URL
    * the scond one the source URL.
    * Return false for preventing the redirect. Return a String (URL) to follow the redirect.
    *
    * @type {Function}
    */
    set redirectFilter(filter) {
        if (typeof filter !== 'function') {
            throw new Error('Filter must be valid function');
        }
        this[redirectFilterKey] = filter;
    }

    /**
    * @ignore
    */
    get redirectFilter() {
        return this[redirectFilterKey] || ((targetUrl) => targetUrl);
    }

    /**
    * This method starts the crawling/scraping process.
    *
    * @param {boolean} [shouldStream=false] Whether to stream the results or use a Promise
    * @return {(Stream|Promise.<Object>)} By default a Promise object is returned that resolves to the result. If streaming is enabled it returns a JSON stream of the results.
    */
    crawl(shouldStream) {
        const crawlTimer = new NanoTimer();
        let stream;
        if (shouldStream) {
            stream = JSONStream.stringifyObject();
        }

        logger.info(`Starting to crawl. Concurrent PhantomJS browsers: ${this.concurrency}.`);
        const pool = createPhantomPool(logger, this.concurrency, this.phantomParameters, this.browserCookies);

        const promise = new Promise((resolve) => {
            if (!this.url) {
                throw new Error(`Defined url '${this.url}' is not valid.`);
            }
            const seen = new Map();
            crawlTimer.time((stopCrawlTimer) => {
                let addUrl;
                const q = async.queue((scope, workerFinished) => {
                    scope.tries++;
                    const workerLogPrefix = `crawlkit:task(${scope.id})`;
                    const workerLogger = {
                        debug: debug(`${workerLogPrefix}:debug`),
                        info: debug(`${workerLogPrefix}:info`),
                        error: debug(`${workerLogPrefix}:error`),
                    };
                    const workerTimer = new NanoTimer();

                    logger.info(`Worker started - ${q.length()} task(s) left in the queue.`);
                    workerLogger.info(`Took ${scope.url} from queue` + (scope.tries > 1 ? ` (attempt ${scope.tries})` : '') + '.');
                    workerTimer.time((stopWorkerTimer) => {
                        async.waterfall([
                            step.acquireBrowser(scope, workerLogger, pool),
                            step.createPage(scope, workerLogger),
                            step.setPageSettings(scope, workerLogger, this.phantomPageSettings, this.followRedirects),
                            step.openPage(scope, workerLogger, addUrl, this.followRedirects, this.redirectFilter),
                            step.findLinks(scope, workerLogger, getFinder(this), getFinderParameters(this), addUrl, this.timeout),
                            step.pageRunners(scope, workerLogger, getRunners(this), workerLogPrefix, this.timeout),
                        ], (err) => {
                            if (err) {
                                workerLogger.error(err);
                                scope.result.error = err;
                            }

                            if (scope.page) {
                                workerLogger.debug(`Page closed.`);
                                scope.page.close();
                            }
                            if (scope.browser) {
                                if (err && (err instanceof HeadlessError)) {
                                    // take no chances - if there was an error on Phantom side, we should get rid of the instance
                                    workerLogger.info(`Attempting to detroy Phantom instance.`);
                                    pool.destroy(scope.browser);
                                    scope.browser = null;
                                } else {
                                    workerLogger.debug(`Phantom released to pool.`);
                                    pool.release(scope.browser);
                                }
                            }
                            stopWorkerTimer();
                            if (err instanceof HeadlessError && scope.tries < this.retries) {
                                logger.info(`Retrying ${scope.url} - adding back to queue`);
                                delete scope.result.error;
                                q.unshift(scope);
                                return workerFinished();
                            }
                            if (shouldStream) {
                                stream.write([scope.url, scope.result]);
                            }
                            workerFinished(err);
                        });
                    }, '', 'm', (workerRuntime) => {
                        workerLogger.info('Finished. Took %sms.', workerRuntime);
                    });
                }, this.concurrency);

                q.drain = () => {
                    stopCrawlTimer();
                    pool.drain(() => pool.destroyAllNow());
                    if (shouldStream) {
                        stream.end();
                        resolve();
                    } else {
                        const result = {
                            results: transformMapToObject(seen),
                        };
                        resolve(result);
                    }
                };

                addUrl = (u) => {
                    let url = urijs(u);
                    url = url.absoluteTo(defaultAbsoluteTo);
                    url.normalize();
                    url = url.toString();

                    if (!seen.has(url)) {
                        logger.info(`Adding ${url}`);
                        const result = {};
                        // don't keep result in memory if we stream
                        seen.set(url, shouldStream ? null : result);
                        q.push({
                            tries: 0,
                            url,
                            result,
                            id: new Chance().name(),
                        });
                    } else {
                        logger.debug(`Skipping ${url} - already seen.`);
                    }
                };

                addUrl(this.url);
            }, '', 's', (time) => {
                logger.info(`Finished. Processed ${seen.size} discovered URLs. Took ${time}s.`);
            });
        });
        if (shouldStream) {
            promise.catch((err) => {
                logger.error(err);
                throw err;
            });
            return stream;
        }
        return promise;
    }
}

module.exports = CrawlKit;
<commit_msg>chore: add logging to queue drain<commit_after>'use strict'; // eslint-disable-line

const HeadlessError = require('node-phantom-simple/headless_error');

const async = require('async');
const debug = require('debug');
const urijs = require('urijs');

const NanoTimer = require('nanotimer');
const Chance = require('chance');
const JSONStream = require('JSONStream');
const createPhantomPool = require('./createPhantomPool.js');

const step = {
    acquireBrowser: require('./worker/steps/acquireBrowser.js'),
    setPageSettings: require('./worker/steps/setPageSettings.js'),
    createPage: require('./worker/steps/createPage.js'),
    openPage: require('./worker/steps/openPage.js'),
    findLinks: require('./worker/steps/findLinks.js'),
    pageRunners: require('./worker/steps/pageRunners.js'),
};

const logger = {
    debug: debug('crawlkit:debug'),
    info: debug('crawlkit:info'),
    error: debug('crawlkit:error'),
};

const concurrencyKey = Symbol();
const urlKey = Symbol();
const finderKey = Symbol();
const timeoutKey = Symbol();
const runnerKey = Symbol();
const phantomParamsKey = Symbol();
const phantomPageSettingsKey = Symbol();
const followRedirectsKey = Symbol();
const browserCookiesKey = Symbol();
const retriesKey = Symbol();
const redirectFilterKey = Symbol();

/**
* Transforms a {Map} to an {Object} hash.
*
* @private
* @param {Map} map The map to transform
* @return {Object} The transformed key/value hash object.
*/
function transformMapToObject(map) {
    const result = {};
    map.forEach((value, key) => {
        result[key] = value;
    });
    return result;
}

/**
* Gets a finder definition of a {@link CrawlKit} instance.
* @private
* @return {Finder} the finder instance set via {@link CrawlKit#setFinder}.
*/
function getFinder(crawlerInstance) {
    return crawlerInstance[finderKey].finder;
}

function getFinderParameters(crawlerInstance) {
    return crawlerInstance[finderKey].parameters;
}

function getRunners(crawlerInstance) {
    return crawlerInstance[runnerKey];
}

/**
* The protocol a URL without a protocol is written to.
*
* @private
* @type {String}
*/
const defaultAbsoluteTo = 'http://';

/**
* The CrawlKit base class. This is where the magic happens.
*/
class CrawlKit {

    /**
    * Create a CrawlKit instance
    * @constructor
    * @param {String} [url] The start URL. Sets the {@link CrawlKit#url}.
    * @return {CrawlKit} a new CrawlKit instance
    */
    constructor(url) {
        if (url) {
            this.url = url;
        }
        this[runnerKey] = new Map();
        this[finderKey] = {};
        this[browserCookiesKey] = [];
    }

    /**
    * Getter/setter for the timeout in ms for runners and finder functions.
    * The timeout starts fresh for each runner.
    *
    * Values under zero are set to zero.
    *
    * @type {!integer}
    * @default 10000 (10 seconds)
    */
    set timeout(num) {
        this[timeoutKey] = parseInt(num, 10);
    }

    /**
    * @ignore
    */
    get timeout() {
        return Math.max(0, this[timeoutKey] || 10000);
    }

    /**
    * Getter/setter for the concurrency of the crawler.
    * This controls the amount of PhantomJS instances that will be spawned
    * and used to work on found websites. Adapt this to the power of your machine.
    *
    * Values under one are set to one.
    *
    * @type {!integer}
    * @default 1 (No concurrency)
    */
    set concurrency(num) {
        this[concurrencyKey] = parseInt(num, 10);
    }

    /**
    * @ignore
    */
    get concurrency() {
        return Math.max(1, this[concurrencyKey] || 1);
    }

    /**
    * Getter/setter for the start URL of the crawler.
    * This is the URL that will be used as an initial endpoint for the crawler.
    * If the protocol is omitted (e.g. URL starts with //), the URL will be rewritten to http://
    * @type {String}
    */
    set url(str) {
        this[urlKey] = str;
    }

    /**
    * @ignore
    */
    get url() {
        return this[urlKey];
    }

    /**
    * With this method a {@link Finder} instance can be set for the crawler.
    * A finder is used for link discovery on a website. It is run directly after page load
    * and is optional (e.g. if you want to only work on a single page).
    *
    * @param {!Finder} finder The finder instance to use for discovery.
    * @param {...*} [runnableParams] These parameters are passed to the function returned by {@link Finder#getRunnable} at evaluation time.
    */
    setFinder(finder /* parameters... */) {
        if (!finder || typeof finder.getRunnable !== 'function') {
            throw new Error('Not a valid finder instance');
        }

        this[finderKey].finder = finder;
        this[finderKey].parameters = Array.prototype.slice.call(arguments, 1);
    }

    /**
    * Getter/setter for the number of retries when a PhantomJS instance crashes on a page.
    * When a PhantomJS instance crashes whilst crawling a webpage, this instance is shutdown
    * and replaced by a new one. By default the webpage that failed in such a way will be
    * re-queued. This member controls how often that re-queueing happens.
    *
    * Values under zero are set to zero.
    *
    * @type {!integer}
    * @default 3 (try 2 more times after the first failure)
    */
    set retries(n) {
        this[retriesKey] = parseInt(n, 10);
    }

    /**
    * @ignore
    */
    get retries() {
        return Math.max(0, this[retriesKey] || 3);
    }

    /**
    * Allows you to add a runner that is executed on each crawled page.
    * The returned value of the runner is added to the overall result.
    * Runners run sequentially on each webpage in the order they were added.
    * If a runner is crashing PhantomJS more than {@link CrawlKit#retries} times, subsequent {@link Runner}s are not executed.
    *
    * @see For an example see `examples/simple.js`. For an example using parameters, see `examples/advanced.js`.
    * @param {!String} key The runner identificator. This is also used in the result stream/object.
    * @param {!Runner} runner The runner instance to use for discovery.
    * @param {...*} [runnableParams] These parameters are passed to the function returned by {@link Runner#getRunnable} at evaluation time.
    */
    addRunner(key, runner /* args ... */) {
        if (!key) {
            throw new Error('Not a valid runner key');
        }
        if (!runner || typeof runner.getCompanionFiles !== 'function' || typeof runner.getRunnable !== 'function') {
            throw new Error('Not a valid runner instance');
        }

        const parameters = Array.prototype.slice.call(arguments, 2);

        this[runnerKey].set(key, {
            runner,
            parameters,
        });
    }

    /**
    * Getter/setter for the map of parameters to pass to PhantomJS.
    * You can use this for example to ignore SSL errors.
    * For a list of parameters, please refer to the [PhantomJS documentation]{@link http://phantomjs.org/api/command-line.html}.
    *
    * @type {!Object.<String,String>}
    */
    set phantomParameters(params) {
        this[phantomParamsKey] = params;
    }

    /**
    * @ignore
    */
    get phantomParameters() {
        return this[phantomParamsKey] || {};
    }

    /**
    * Getter/setter for the map of settings to pass to an opened page.
    * You can use this for example for Basic Authentication.
    * For a list of options, please refer to the [PhantomJS documentation]{@link http://phantomjs.org/api/webpage/property/settings.html}.
    * Nested settings can just be provided in dot notation as the key, e.g. 'settings.userAgent'.
    *
    * @type {!Object.<String,*>}
    */
    set phantomPageSettings(settings) {
        this[phantomPageSettingsKey] = settings;
    }

    /**
    * @ignore
    */
    get phantomPageSettings() {
        return this[phantomPageSettingsKey] || {};
    }

    /**
    * Getter/setter for whether to follow redirects or not.
    * When following redirects, the original page is not processed.
    *
    * @type {!boolean}
    * @default false
    */
    set followRedirects(value) {
        this[followRedirectsKey] = !!value;
    }

    /**
    * @ignore
    */
    get followRedirects() {
        return this[followRedirectsKey] || false;
    }

    /**
    * Getter/setter for the cookies to set within PhantomJS.
    * Each entry is supposed to be an object [following the PhantomJS spec]{@link http://phantomjs.org/api/webpage/method/add-cookie.html}.
    *
    * @type {!Array.<Object>}
    */
    set browserCookies(cookies) {
        if (!(cookies instanceof Array)) {
            throw new Error('Not properly munchable');
        }
        this[browserCookiesKey] = cookies;
    }

    /**
    * @ignore
    */
    get browserCookies() {
        return this[browserCookiesKey];
    }

    /**
    * Getter/setter for the filter that is applied to redirected URLs.
    * With this filter you can prevent the redirect or rewrite it.
    * The filter callback gets two arguments. The first one is the target URL
    * the scond one the source URL.
    * Return false for preventing the redirect. Return a String (URL) to follow the redirect.
    *
    * @type {Function}
    */
    set redirectFilter(filter) {
        if (typeof filter !== 'function') {
            throw new Error('Filter must be valid function');
        }
        this[redirectFilterKey] = filter;
    }

    /**
    * @ignore
    */
    get redirectFilter() {
        return this[redirectFilterKey] || ((targetUrl) => targetUrl);
    }

    /**
    * This method starts the crawling/scraping process.
    *
    * @param {boolean} [shouldStream=false] Whether to stream the results or use a Promise
    * @return {(Stream|Promise.<Object>)} By default a Promise object is returned that resolves to the result. If streaming is enabled it returns a JSON stream of the results.
    */
    crawl(shouldStream) {
        const crawlTimer = new NanoTimer();
        let stream;
        if (shouldStream) {
            stream = JSONStream.stringifyObject();
        }

        logger.info(`Starting to crawl. Concurrent PhantomJS browsers: ${this.concurrency}.`);
        const pool = createPhantomPool(logger, this.concurrency, this.phantomParameters, this.browserCookies);

        const promise = new Promise((resolve) => {
            if (!this.url) {
                throw new Error(`Defined url '${this.url}' is not valid.`);
            }
            const seen = new Map();
            crawlTimer.time((stopCrawlTimer) => {
                let addUrl;
                const q = async.queue((scope, workerFinished) => {
                    scope.tries++;
                    const workerLogPrefix = `crawlkit:task(${scope.id})`;
                    const workerLogger = {
                        debug: debug(`${workerLogPrefix}:debug`),
                        info: debug(`${workerLogPrefix}:info`),
                        error: debug(`${workerLogPrefix}:error`),
                    };
                    const workerTimer = new NanoTimer();

                    logger.info(`Worker started - ${q.length()} task(s) left in the queue.`);
                    workerLogger.info(`Took ${scope.url} from queue` + (scope.tries > 1 ? ` (attempt ${scope.tries})` : '') + '.');
                    workerTimer.time((stopWorkerTimer) => {
                        async.waterfall([
                            step.acquireBrowser(scope, workerLogger, pool),
                            step.createPage(scope, workerLogger),
                            step.setPageSettings(scope, workerLogger, this.phantomPageSettings, this.followRedirects),
                            step.openPage(scope, workerLogger, addUrl, this.followRedirects, this.redirectFilter),
                            step.findLinks(scope, workerLogger, getFinder(this), getFinderParameters(this), addUrl, this.timeout),
                            step.pageRunners(scope, workerLogger, getRunners(this), workerLogPrefix, this.timeout),
                        ], (err) => {
                            if (err) {
                                workerLogger.error(err);
                                scope.result.error = err;
                            }

                            if (scope.page) {
                                workerLogger.debug(`Page closed.`);
                                scope.page.close();
                            }
                            if (scope.browser) {
                                if (err && (err instanceof HeadlessError)) {
                                    // take no chances - if there was an error on Phantom side, we should get rid of the instance
                                    workerLogger.info(`Attempting to detroy Phantom instance.`);
                                    pool.destroy(scope.browser);
                                    scope.browser = null;
                                } else {
                                    workerLogger.debug(`Phantom released to pool.`);
                                    pool.release(scope.browser);
                                }
                            }
                            stopWorkerTimer();
                            if (err instanceof HeadlessError && scope.tries < this.retries) {
                                logger.info(`Retrying ${scope.url} - adding back to queue`);
                                delete scope.result.error;
                                q.unshift(scope);
                                return workerFinished();
                            }
                            if (shouldStream) {
                                stream.write([scope.url, scope.result]);
                            }
                            workerFinished(err);
                        });
                    }, '', 'm', (workerRuntime) => {
                        workerLogger.info('Finished. Took %sms.', workerRuntime);
                    });
                }, this.concurrency);

                q.drain = () => {
                    logger.debug('Queue empty. Stopping crawler timer');

                    stopCrawlTimer();
                    logger.debug('Draining pool');
                    pool.drain(() => pool.destroyAllNow());

                    if (shouldStream) {
                        stream.end();
                        resolve();
                    } else {
                        const result = {
                            results: transformMapToObject(seen),
                        };
                        resolve(result);
                    }
                };

                addUrl = (u) => {
                    let url = urijs(u);
                    url = url.absoluteTo(defaultAbsoluteTo);
                    url.normalize();
                    url = url.toString();

                    if (!seen.has(url)) {
                        logger.info(`Adding ${url}`);
                        const result = {};
                        // don't keep result in memory if we stream
                        seen.set(url, shouldStream ? null : result);
                        q.push({
                            tries: 0,
                            url,
                            result,
                            id: new Chance().name(),
                        });
                    } else {
                        logger.debug(`Skipping ${url} - already seen.`);
                    }
                };

                addUrl(this.url);
            }, '', 's', (time) => {
                logger.info(`Finished. Processed ${seen.size} discovered URLs. Took ${time}s.`);
            });
        });
        if (shouldStream) {
            promise.catch((err) => {
                logger.error(err);
                throw err;
            });
            return stream;
        }
        return promise;
    }
}

module.exports = CrawlKit;
<|endoftext|>


<commit_before>import path from 'path';
import fs from 'fs';
import collect from './collect';

let runOnAllFiles;

function hasFlowPragma(source) {
  return source
    .getAllComments()
    .some(comment => /@flow/.test(comment.value));
}

export default {
  rules: {
    'show-errors': function showErrors(context) {
      return {
        Program() {
          const onTheFly = true;
          let collected;

          if (onTheFly) {
            const source = context.getSourceCode();
            const root = process.cwd();
            const flowDirSetting = context.settings
              && context.settings['flowtype-errors']
              && context.settings['flowtype-errors'].flowDir;

            const flowDir = fs.existsSync(path.join(root, flowDirSetting, '.flowconfig'))
              ? path.join(root, flowDirSetting)
              : root;

            // Check to see if we should run on every file
            if (runOnAllFiles === undefined) {
              try {
                runOnAllFiles = fs
                  .readFileSync(path.join(flowDir, '.flowconfig'))
                  .toString()
                  .includes('all=true');
              } catch (err) {
                runOnAllFiles = false;
              }
            }

            if (runOnAllFiles === false && !hasFlowPragma(source)) {
              return true;
            }

            collected = collect(source.getText(), flowDir, context.getFilename());
          } else {
            collected = collect();
          }

          const pluginErrors = Array.isArray(collected)
            ? (onTheFly ? collected : collected.filter(
                each => each.path === context.getFilename()
              ))
            : [];

          pluginErrors.forEach(({ loc, message }) => {
            context.report({
              loc,
              message
            });
          });
        }
      };
    }
  }
};
<commit_msg>Add missing default value<commit_after>import path from 'path';
import fs from 'fs';
import collect from './collect';

let runOnAllFiles;

function hasFlowPragma(source) {
  return source
    .getAllComments()
    .some(comment => /@flow/.test(comment.value));
}

export default {
  rules: {
    'show-errors': function showErrors(context) {
      return {
        Program() {
          const onTheFly = true;
          let collected;

          if (onTheFly) {
            const source = context.getSourceCode();
            const root = process.cwd();
            const flowDirSetting = context.settings
              && context.settings['flowtype-errors']
              && context.settings['flowtype-errors'].flowDir || '.';

            const flowDir = fs.existsSync(path.join(root, flowDirSetting, '.flowconfig'))
              ? path.join(root, flowDirSetting)
              : root;

            // Check to see if we should run on every file
            if (runOnAllFiles === undefined) {
              try {
                runOnAllFiles = fs
                  .readFileSync(path.join(flowDir, '.flowconfig'))
                  .toString()
                  .includes('all=true');
              } catch (err) {
                runOnAllFiles = false;
              }
            }

            if (runOnAllFiles === false && !hasFlowPragma(source)) {
              return true;
            }

            collected = collect(source.getText(), flowDir, context.getFilename());
          } else {
            collected = collect();
          }

          const pluginErrors = Array.isArray(collected)
            ? (onTheFly ? collected : collected.filter(
                each => each.path === context.getFilename()
              ))
            : [];

          pluginErrors.forEach(({ loc, message }) => {
            context.report({
              loc,
              message
            });
          });
        }
      };
    }
  }
};
<|endoftext|>


<commit_before>// Components
export Button from './components/button';
export Checkbox from './components/checkbox';
export Divider from './components/divider';
export Fab from './components/fab';
export Icon from './components/icon';
export IconButton from './components/icon-button';
export Label from './components/label';
export Parallax from './components/parallax';
export Progress from './components/progress';
export Ripple from './components/ripple';
export Spinner from './components/spinner';
export Background from './components/background';
export Switch from './components/switch';

// Styles
export Theme from './styles/theme';
<commit_msg>Sorted the exports of the src/index file to match the sorting in the src/components directory<commit_after>// Components
export Background from './components/background';
export Button from './components/button';
export Checkbox from './components/checkbox';
export Divider from './components/divider';
export EventHandler from './components/event-handler';
export Fab from './components/fab';
export Icon from './components/icon';
export IconButton from './components/icon-button';
export Label from './components/label';
export Parallax from './components/parallax';
export Progress from './components/progress';
export Ripple from './components/ripple';
export Spinner from './components/spinner';
export Switch from './components/switch';

// Styles
export Theme from './styles/theme';
<|endoftext|>


<commit_before>import path from 'path'
import express from 'express'
import {expressHelpers, run} from 'yacol'
import cookieParser from 'cookie-parser'
import fetch from 'node-fetch'
import c from './config'
import {authorizeUrl, accessToken, amICollaborator as _amICollaborator, unauthorized} from './ghApi.js'
import memoize from './memoize'
import createS3Client from './s3.js'

const app = express()
const {register, runApp} = expressHelpers
const amICollaborator = memoize(_amICollaborator, c.cacheMaxRecords, c.authorizationMaxAge)
const s3 = createS3Client(c.s3)

app.use(cookieParser())

function sendToLogin(req, res) {
  res.cookie('redirectAfterLogin', req.url, {httpOnly: true, secure: c.isHttps})
  res.redirect(r.login)
}

function* checkRights(req, repo) {
  if (repo == null) return true

  const token = req.cookies.access_token
  if (!token) throw unauthorized

  return yield run(amICollaborator, token, c.ghOrganization, repo)
}

function* login(req, res) {
  res.redirect(authorizeUrl(c.ghClient))
}

function* oauth(req, res) {
  const token = yield run(accessToken, c.ghClient, req.query.code)

  if (token) {
    res.cookie('access_token', token, {httpOnly: true, secure: c.isHttps})
    res.redirect(req.cookies.redirectAfterLogin || r.index)
  } else {
    res.redirect(r.login)
  }
}

const validDocId = (docId) => docId && docId.match(/^[a-zA-Z0-9-_]*$/)

function* serveDoc(docId, localPart, req, res) {
  localPart = path.normalize(localPart || '/')
  if (localPart.endsWith('/')) localPart += 'index.html'

  const isReqValid = validDocId(docId) && !localPart.startsWith('..')

  if (!isReqValid) {
    res.status(404).send('Not Found')
    return
  }

  const docRoot = path.join(c.draftPath, docId)

  const configFile = path.join(docRoot, 'docs.json')
  const config = yield run(function*() {
    const file = yield run(s3.readFile, configFile)
    return JSON.parse(file.Body.toString())
  }).catch((e) => {
    if (e.code === 'NoSuchKey') return {}
    else throw e
  })

  yield run(function*() {
    const hasRights = yield run(checkRights, req, config.read)

    if (hasRights) {
      yield run(function*() {
        const file = yield run(s3.readFile, path.join(docRoot, localPart))
        res.set('Content-Type', file.ContentType)
        res.set('Content-Length', file.ContentLength)
        res.send(file.Body)
      }).catch((e) => {
        if (e.code === 'NoSuchKey') res.status(404).send('Not Found')
        else throw e
      })
    } else {
      res.status(401).send('You do not have rights to access these docs.')
    }
  }).catch((e) => {
    if (e === unauthorized) sendToLogin(req, res)
    else throw e
  })
}

function* drafts(req, res) {
  yield run(serveDoc, req.params.docId, req.params[0], req, res)
}

function* docs(req, res) {
  const docId = yield run(function*() {
    const file = yield run(s3.readFile, path.join(c.finalPath, req.params.name))
    return file.Body.toString()
  }).catch((e) => {
    if (e.code === 'NoSuchKey') res.status(404).send('Not Found')
    else throw e
  })

  yield run(serveDoc, docId, req.params[0], req, res)
}

function assertApiKey(req, res) {
  if (req.get('Authorization') !== c.apiKey) {
    res.status(401).send('Invalid API Key')
    return false
  }
  return true
}

function* upload(req, res) {
  if (!assertApiKey(req, res)) return

  const docId = Math.floor((Date.now() + Math.random())*1000).toString(36)
  yield run(s3.unzip, req, path.join(c.draftPath, docId))
  res.status(200).send(docId)
}

function* alias(req, res) {
  if (!assertApiKey(req, res)) return

  const {docId, name} = req.params
  const isReqValid = validDocId(docId) && validDocId(name)
  if (!isReqValid) {
    res.status(400).send('Invalid request.')
    return
  }

  yield run(s3.writeFile, path.join(c.finalPath, name), docId)
  res.status(200).send()
}

function* index(req, res) {
  res.send('Nothing interesting here.')
}

const r = {
  index: '/',
  login: '/$login',
  oauth: '/$oauth',
  drafts: '/$drafts/:docId/*?',
  upload: '/$upload',
  alias: '/$alias/:docId/:name',
  docs: '/:name/*?',
}

const esc = (s) => s.replace('$', '\\$')

register(app, 'get', esc(r.index), index)
register(app, 'get', esc(r.login), login)
register(app, 'get', esc(r.oauth), oauth)
register(app, 'get', esc(r.drafts), drafts)
register(app, 'post', esc(r.upload), upload)
register(app, 'put', esc(r.alias), alias)

// Has to be the last one, otherwise it would match all other routes.
register(app, 'get', r.docs, docs)

run(function* () {
  run(runApp)
  app.listen(c.port, () =>
    console.log(`App started on localhost:${c.port}.`)
  )
})
<commit_msg>Factor out saving cookie to function.<commit_after>import path from 'path'
import express from 'express'
import {expressHelpers, run} from 'yacol'
import cookieParser from 'cookie-parser'
import fetch from 'node-fetch'
import c from './config'
import {authorizeUrl, accessToken, amICollaborator as _amICollaborator, unauthorized} from './ghApi.js'
import memoize from './memoize'
import createS3Client from './s3.js'

const app = express()
const {register, runApp} = expressHelpers
const amICollaborator = memoize(_amICollaborator, c.cacheMaxRecords, c.authorizationMaxAge)
const s3 = createS3Client(c.s3)

app.use(cookieParser())

function saveCookie(res, key, value) {
  res.cookie(key, value, {httpOnly: true, secure: c.isHttps})
}

function sendToLogin(req, res) {
  saveCookie(res, 'redirectAfterLogin', req.url)
  res.redirect(r.login)
}

function* checkRights(req, repo) {
  if (repo == null) return true

  const token = req.cookies.access_token
  if (!token) throw unauthorized

  return yield run(amICollaborator, token, c.ghOrganization, repo)
}

function* login(req, res) {
  res.redirect(authorizeUrl(c.ghClient))
}

function* oauth(req, res) {
  const token = yield run(accessToken, c.ghClient, req.query.code)

  if (token) {
    saveCookie(res, 'access_token', token)
    res.redirect(req.cookies.redirectAfterLogin || r.index)
  } else {
    res.redirect(r.login)
  }
}

const validDocId = (docId) => docId && docId.match(/^[a-zA-Z0-9-_]*$/)

function* serveDoc(docId, localPart, req, res) {
  localPart = path.normalize(localPart || '/')
  if (localPart.endsWith('/')) localPart += 'index.html'

  const isReqValid = validDocId(docId) && !localPart.startsWith('..')

  if (!isReqValid) {
    res.status(404).send('Not Found')
    return
  }

  const docRoot = path.join(c.draftPath, docId)

  const configFile = path.join(docRoot, 'docs.json')
  const config = yield run(function*() {
    const file = yield run(s3.readFile, configFile)
    return JSON.parse(file.Body.toString())
  }).catch((e) => {
    if (e.code === 'NoSuchKey') return {}
    else throw e
  })

  yield run(function*() {
    const hasRights = yield run(checkRights, req, config.read)

    if (hasRights) {
      yield run(function*() {
        const file = yield run(s3.readFile, path.join(docRoot, localPart))
        res.set('Content-Type', file.ContentType)
        res.set('Content-Length', file.ContentLength)
        res.send(file.Body)
      }).catch((e) => {
        if (e.code === 'NoSuchKey') res.status(404).send('Not Found')
        else throw e
      })
    } else {
      res.status(401).send('You do not have rights to access these docs.')
    }
  }).catch((e) => {
    if (e === unauthorized) sendToLogin(req, res)
    else throw e
  })
}

function* drafts(req, res) {
  yield run(serveDoc, req.params.docId, req.params[0], req, res)
}

function* docs(req, res) {
  const docId = yield run(function*() {
    const file = yield run(s3.readFile, path.join(c.finalPath, req.params.name))
    return file.Body.toString()
  }).catch((e) => {
    if (e.code === 'NoSuchKey') res.status(404).send('Not Found')
    else throw e
  })

  yield run(serveDoc, docId, req.params[0], req, res)
}

function assertApiKey(req, res) {
  if (req.get('Authorization') !== c.apiKey) {
    res.status(401).send('Invalid API Key')
    return false
  }
  return true
}

function* upload(req, res) {
  if (!assertApiKey(req, res)) return

  const docId = Math.floor((Date.now() + Math.random())*1000).toString(36)
  yield run(s3.unzip, req, path.join(c.draftPath, docId))
  res.status(200).send(docId)
}

function* alias(req, res) {
  if (!assertApiKey(req, res)) return

  const {docId, name} = req.params
  const isReqValid = validDocId(docId) && validDocId(name)
  if (!isReqValid) {
    res.status(400).send('Invalid request.')
    return
  }

  yield run(s3.writeFile, path.join(c.finalPath, name), docId)
  res.status(200).send()
}

function* index(req, res) {
  res.send('Nothing interesting here.')
}

const r = {
  index: '/',
  login: '/$login',
  oauth: '/$oauth',
  drafts: '/$drafts/:docId/*?',
  upload: '/$upload',
  alias: '/$alias/:docId/:name',
  docs: '/:name/*?',
}

const esc = (s) => s.replace('$', '\\$')

register(app, 'get', esc(r.index), index)
register(app, 'get', esc(r.login), login)
register(app, 'get', esc(r.oauth), oauth)
register(app, 'get', esc(r.drafts), drafts)
register(app, 'post', esc(r.upload), upload)
register(app, 'put', esc(r.alias), alias)

// Has to be the last one, otherwise it would match all other routes.
register(app, 'get', r.docs, docs)

run(function* () {
  run(runApp)
  app.listen(c.port, () =>
    console.log(`App started on localhost:${c.port}.`)
  )
})
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2018 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

'use strict';

const isEmpty = require('lodash.isempty');
const omitBy = require('lodash.omitby');
const MagicString = require('magic-string');
const diff = require('diff');
const prettier = require('prettier');

const NAME = 'rollup-plugin-prettier';

module.exports = (options) => {
  let sourcemap = null;
  let newOptions = options;

  if (newOptions && hasSourceMap(newOptions)) {
    sourcemap = isSourceMapEnabled(newOptions);
  }

  // Try to resolve config file it it exists
  const cwd = newOptions && hasCwd(newOptions) ? newOptions.cwd : process.cwd();
  const configOptions = prettier.resolveConfig.sync(cwd);
  if (configOptions != null) {
    newOptions = Object.assign(configOptions, newOptions || {});
  }

  newOptions = omitSourceMap(newOptions);
  newOptions = omitCwd(newOptions);

  // Do not send an empty option object.
  if (isEmpty(newOptions)) {
    newOptions = undefined;
  }

  return {
    /**
     * Plugin name (used by rollup for error messages and warnings).
     * @type {string}
     */
    name: NAME,

    /**
     * Function called by `rollup` that is used to read the `sourceMap` setting.
     *
     * @param {Object} opts Rollup options.
     * @return {void}
     */
    options(opts = {}) {
      if (sourcemap == null) {
        // Get the global `sourcemap` option on given object.
        // Should support:
        //  - `sourcemap` (lowercase) option which is the name with rollup >= 0.48.0,
        //  - `sourceMap` (camelcase) option which is the (deprecated) name with rollup < 0.48.0.
        const globalSourcemap = isSourceMapEnabled(opts);

        // Since rollup 0.48, sourcemap option can be set on the `output` object.
        const output = opts.output || {};
        const outputSourceMap = Array.isArray(output) ? output.some(isSourceMapEnabled) : isSourceMapEnabled(output);

        // Enable or disable `sourcemap` generation.
        sourcemap = globalSourcemap || outputSourceMap;
      }
    },

    /**
     * Function called by `rollup` before generating final bundle.
     *
     * @param {string} source Souce code of the final bundle.
     * @param {Object} outputOptions Output option.
     * @return {Object} The result containing a `code` property and, if a enabled, a `map` property.
     */
    transformBundle(source, outputOptions) {
      const output = prettier.format(source, newOptions);
      const outputOptionsSourcemap = outputOptions == null ? null : isSourceMapEnabled(outputOptions);

      // Should we generate sourcemap?
      // The sourcemap option may be a boolean or any truthy value (such as a `string`).
      // Note that this option should be false by default as it may take a (very) long time.
      if (!sourcemap && !outputOptionsSourcemap) {
        return {code: output};
      }

      console.log(`[${NAME}] Sourcemap is enabled, computing diff is required`);
      console.log(`[${NAME}] This may take a moment (depends on the size of your bundle)`);

      const magicString = new MagicString(source);
      const changes = diff.diffChars(source, output);

      if (changes && changes.length > 0) {
        let idx = 0;

        changes.forEach((part) => {
          if (part.added) {
            magicString.prependLeft(idx, part.value);
            idx -= part.count;
          } else if (part.removed) {
            magicString.remove(idx, idx + part.count);
          }

          idx += part.count;
        });
      }

      return {
        code: magicString.toString(),
        map: magicString.generateMap({
          hires: true,
        }),
      };
    },
  };
};

const SOURCE_MAPS_OPTS = [
  'sourcemap', // Name of the property with rollup >= 0.48.
  'sourceMap', // Name of the property with rollup < 0.48.
];

/**
 * Check if given option object has a `cwd` entry.
 *
 * @param {Object} opts Option object.
 * @return {boolean} `true` if `opts` has `cwd` entry, `false` otherwise.
 */
function hasCwd(opts) {
  return 'cwd' in opts;
}

/**
 * Create option object from given one removing `cwd` key if present.
 *
 * @param {Object} opts Option object.
 * @return {Object} New option object.
 */
function omitCwd(opts) {
  return omitBy(opts, (v, k) => (
    k === 'cwd'
  ));
}

/**
 * Check if `sourcemap` option is defined on option object.
 *
 * @param {Object} opts Options.
 * @return {boolean} `true` if sourcemap is defined, `false` otherwise.
 */
function hasSourceMap(opts) {
  return SOURCE_MAPS_OPTS.some((p) => (
    p in opts
  ));
}

/**
 * Check if `sourcemap` option is enable or not.
 *
 * @param {Object} opts Options.
 * @return {boolean} `true` if sourcemap is enabled, `false` otherwise.
 */
function isSourceMapEnabled(opts) {
  return !!(SOURCE_MAPS_OPTS.find((p) => opts[p]));
}

/**
 * Delete sourcemap option on object.
 *
 * @param {Object} opts The object.
 * @return {Object} Option object without `sourcemap` entry.
 */
function omitSourceMap(opts) {
  return omitBy(opts, (value, key) => (
    SOURCE_MAPS_OPTS.indexOf(key) >= 0
  ));
}
<commit_msg>fix: dot not fail with old version of prettier<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2018 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

'use strict';

const isEmpty = require('lodash.isempty');
const omitBy = require('lodash.omitby');
const MagicString = require('magic-string');
const diff = require('diff');
const prettier = require('prettier');

const NAME = 'rollup-plugin-prettier';

module.exports = (options) => {
  let sourcemap = null;
  let newOptions = options;

  if (newOptions && hasSourceMap(newOptions)) {
    sourcemap = isSourceMapEnabled(newOptions);
  }

  // Try to resolve config file it it exists
  // Be careful, `resolveConfig` function does not exist on old version of prettier.
  if (prettier.resolveConfig) {
    const cwd = newOptions && hasCwd(newOptions) ? newOptions.cwd : process.cwd();
    const configOptions = prettier.resolveConfig.sync(cwd);
    if (configOptions != null) {
      newOptions = Object.assign(configOptions, newOptions || {});
    }
  }

  newOptions = omitSourceMap(newOptions);
  newOptions = omitCwd(newOptions);

  // Do not send an empty option object.
  if (isEmpty(newOptions)) {
    newOptions = undefined;
  }

  return {
    /**
     * Plugin name (used by rollup for error messages and warnings).
     * @type {string}
     */
    name: NAME,

    /**
     * Function called by `rollup` that is used to read the `sourceMap` setting.
     *
     * @param {Object} opts Rollup options.
     * @return {void}
     */
    options(opts = {}) {
      if (sourcemap == null) {
        // Get the global `sourcemap` option on given object.
        // Should support:
        //  - `sourcemap` (lowercase) option which is the name with rollup >= 0.48.0,
        //  - `sourceMap` (camelcase) option which is the (deprecated) name with rollup < 0.48.0.
        const globalSourcemap = isSourceMapEnabled(opts);

        // Since rollup 0.48, sourcemap option can be set on the `output` object.
        const output = opts.output || {};
        const outputSourceMap = Array.isArray(output) ? output.some(isSourceMapEnabled) : isSourceMapEnabled(output);

        // Enable or disable `sourcemap` generation.
        sourcemap = globalSourcemap || outputSourceMap;
      }
    },

    /**
     * Function called by `rollup` before generating final bundle.
     *
     * @param {string} source Souce code of the final bundle.
     * @param {Object} outputOptions Output option.
     * @return {Object} The result containing a `code` property and, if a enabled, a `map` property.
     */
    transformBundle(source, outputOptions) {
      const output = prettier.format(source, newOptions);
      const outputOptionsSourcemap = outputOptions == null ? null : isSourceMapEnabled(outputOptions);

      // Should we generate sourcemap?
      // The sourcemap option may be a boolean or any truthy value (such as a `string`).
      // Note that this option should be false by default as it may take a (very) long time.
      if (!sourcemap && !outputOptionsSourcemap) {
        return {code: output};
      }

      console.log(`[${NAME}] Sourcemap is enabled, computing diff is required`);
      console.log(`[${NAME}] This may take a moment (depends on the size of your bundle)`);

      const magicString = new MagicString(source);
      const changes = diff.diffChars(source, output);

      if (changes && changes.length > 0) {
        let idx = 0;

        changes.forEach((part) => {
          if (part.added) {
            magicString.prependLeft(idx, part.value);
            idx -= part.count;
          } else if (part.removed) {
            magicString.remove(idx, idx + part.count);
          }

          idx += part.count;
        });
      }

      return {
        code: magicString.toString(),
        map: magicString.generateMap({
          hires: true,
        }),
      };
    },
  };
};

const SOURCE_MAPS_OPTS = [
  'sourcemap', // Name of the property with rollup >= 0.48.
  'sourceMap', // Name of the property with rollup < 0.48.
];

/**
 * Check if given option object has a `cwd` entry.
 *
 * @param {Object} opts Option object.
 * @return {boolean} `true` if `opts` has `cwd` entry, `false` otherwise.
 */
function hasCwd(opts) {
  return 'cwd' in opts;
}

/**
 * Create option object from given one removing `cwd` key if present.
 *
 * @param {Object} opts Option object.
 * @return {Object} New option object.
 */
function omitCwd(opts) {
  return omitBy(opts, (v, k) => (
    k === 'cwd'
  ));
}

/**
 * Check if `sourcemap` option is defined on option object.
 *
 * @param {Object} opts Options.
 * @return {boolean} `true` if sourcemap is defined, `false` otherwise.
 */
function hasSourceMap(opts) {
  return SOURCE_MAPS_OPTS.some((p) => (
    p in opts
  ));
}

/**
 * Check if `sourcemap` option is enable or not.
 *
 * @param {Object} opts Options.
 * @return {boolean} `true` if sourcemap is enabled, `false` otherwise.
 */
function isSourceMapEnabled(opts) {
  return !!(SOURCE_MAPS_OPTS.find((p) => opts[p]));
}

/**
 * Delete sourcemap option on object.
 *
 * @param {Object} opts The object.
 * @return {Object} Option object without `sourcemap` entry.
 */
function omitSourceMap(opts) {
  return omitBy(opts, (value, key) => (
    SOURCE_MAPS_OPTS.indexOf(key) >= 0
  ));
}
<|endoftext|>


<commit_before>import fs from 'fs';
import path from 'path';
import Map from 'es6-map';
import assign from 'object-assign';
import npmResolve from 'resolve';
import bowerResolve from 'resolve-bower';

let options;

/**
 * Check for the "main" field to see if it contains any css/scss/sass
 * If not, try to use the "style" field from package.json - and if that fails
 * too, defaults to "index.css"
 */
const filter = (pkg) => {
  if (!pkg.main || (pkg.main && !pkg.main.match(/\.s?[c|a]ss$/g))) {
    pkg.main = pkg.style || 'index.css';
  }
  return pkg;
};

/**
 * Simple Promise wrapper to resolve the npm/bower modules
 */
const find = (resolver, { url, prev, resolved }) => new Promise((resolve) => {
  if (resolved) {
    resolve({ url, prev, resolved });
  } else {
    resolver(url, options, (err, res) => {
      resolve({ url: (err ? url : res), prev, resolved: !err });
    });
  }
});

const npm = (file) => find(npmResolve, file);

const bower = (file) => find(bowerResolve, file);

/**
 * Read file's content
 */
const read = ({ url, prev, resolved }) => new Promise((resolve, reject) => {
  if (url.match(/\.s[c|a]ss/g) || !resolved) {
    let resolvedURL = url;
    if (prev && prev !== 'stdin' && !path.isAbsolute(url)) {
      resolvedURL = path.resolve(path.dirname(prev), url);
    }
    resolve({ file: resolvedURL });
  } else {
    fs.readFile(url, 'utf8', (err, contents) => err ? reject(err) : resolve({ contents }));
  }
});

/**
 * Look for Sass files installed through npm
 * @param opts {Object}       Options to be passed to the resolver module
 *
 * @return {Function}         Function to be used by node-sass importer
 */
export default function (opts) {
  options = assign({}, { packageFilter: filter }, opts);

  const aliases = new Map();

  return (url, prev, done) => {
    if (aliases.has(url)) {
      done(aliases.get(url));
    } else {
      npm({ url, prev }).then(bower).then(read).then((res) => {
        aliases.set(url, res);
        done(res);
      });
    }
  };
}
<commit_msg>Add support to import custom files from npm module<commit_after>import fs from 'fs';
import path from 'path';
import Map from 'es6-map';
import assign from 'object-assign';
import npmResolve from 'resolve';
import bowerResolve from 'resolve-bower';

let options;

/**
 * Check for the "main" field to see if it contains any css/scss/sass
 * If not, try to use the "style" field from package.json - and if that fails
 * too, defaults to "index.css"
 */
const filter = (pkg) => {
  if (!pkg.main || (pkg.main && !pkg.main.match(/\.s?[c|a]ss$/g))) {
    pkg.main = pkg.style || pkg['main.scss'] || pkg['main.sass'] || 'index.css';
  }
  return pkg;
};

/**
 * Simple Promise wrapper to resolve the npm/bower modules
 */
const find = (resolver, { url, prev, resolved }) => new Promise((resolve) => {
  if (resolved) {
    resolve({ url, prev, resolved });
  } else {
    resolver(url, options, (err, res) => {
      resolve({ url: (err ? url : res), prev, resolved: !err });
    });
  }
});

const npm = (file) => find(npmResolve, file);

const bower = (file) => find(bowerResolve, file);

/**
 * Read file's content
 */
const read = ({ url, prev, resolved }) => new Promise((resolve, reject) => {
  if (url.match(/\.css$/g)) {
    fs.readFile(url, 'utf8', (err, contents) => err ? reject(err) : resolve({ contents }));
  } else {
    let resolvedURL = url;
    if (!resolved && prev && prev !== 'stdin' && !path.isAbsolute(url)) {
      resolvedURL = path.resolve(path.dirname(prev), url);
    }
    resolve({ file: resolvedURL });
  }
});

/**
 * Look for Sass files installed through npm
 * @param opts {Object}       Options to be passed to the resolver module
 *
 * @return {Function}         Function to be used by node-sass importer
 */
export default function (opts) {
  options = assign({}, { packageFilter: filter }, opts);

  const aliases = new Map();

  return (url, prev, done) => {
    if (aliases.has(url)) {
      done(aliases.get(url));
    } else {
      npm({ url, prev }).then(bower).then(read).then((res) => {
        aliases.set(url, res);
        done(res);
      });
    }
  };
}
<|endoftext|>


<commit_before>/* globals window */

module.exports = VideoStreamMerger

function VideoStreamMerger (opts) {
  var self = this
  if (!(self instanceof VideoStreamMerger)) return new VideoStreamMerger(opts)

  opts = opts || {}

  var AudioContext = window.AudioContext || window.webkitAudioContext
  var audioSupport = !!(AudioContext && (self._audioCtx = (opts.audioContext || new AudioContext())).createMediaStreamDestination)
  var canvasSupport = !!document.createElement('canvas').captureStream
  var supported = audioSupport && canvasSupport
  if (!supported) {
    throw new Error('Unsupported browser')
  }
  self.width = opts.width || 400
  self.height = opts.height || 300
  self.fps = opts.fps || 25

  // Hidden canvas element for merging
  self._canvas = document.createElement('canvas')
  self._canvas.setAttribute('width', self.width)
  self._canvas.setAttribute('height', self.height)
  self._canvas.setAttribute('style', 'position:fixed; left: 110%; pointer-events: none') // Push off screen
  self._ctx = self._canvas.getContext('2d')

  self._streams = []

  self._audioDestination = self._audioCtx.createMediaStreamDestination()

  self._setupConstantNode() // HACK for wowza #7, #10

  self.started = false
  self.result = null

  self._backgroundAudioHack()
}

VideoStreamMerger.prototype._backgroundAudioHack = function () {
  var self = thiss

  // stop browser from throttling timers by playing almost-silent audio
  var source = self._audioCtx.createConstantSource()
  var gainNode = ctx.createGain()
  gainNode.gain.value = 0.001 // required to prevent popping on start
  source.connect(gainNode)
  gainNode.connect(ctx.destination)
  source.start()
}

VideoStreamMerger.prototype._setupConstantNode = function () {
  var self = this

  var constantAudioNode = self._audioCtx.createConstantSource()
  constantAudioNode.start()

  var gain = self._audioCtx.createGain() // gain node prevents quality drop
  gain.gain.value = 0

  constantAudioNode.connect(gain)
  gain.connect(self._audioDestination)
}

VideoStreamMerger.prototype.updateIndex = function (mediaStream, index) {
  var self = this

  if (typeof mediaStream === 'string') {
    mediaStream = {
      id: mediaStream
    }
  }

  index = index == null ? self._streams.length : index

  for (var i = 0; i < self._streams.length; i++) {
    if (mediaStream.id === self._streams[i].id) {
      var stream = self._streams.splice(i, 1)[0]
      stream.index = index
      self._streams.splice(stream.index, 0, stream)
    }
  }
}

VideoStreamMerger.prototype.addStream = function (mediaStream, opts) {
  var self = this

  if (typeof mediaStream === 'string') {
    return self._addData(mediaStream, opts)
  }

  opts = opts || {}
  var stream = {}

  stream.isData = false
  stream.x = opts.x || 0
  stream.y = opts.y || 0
  stream.width = opts.width || self.width
  stream.height = opts.height || self.height
  stream.draw = opts.draw || null
  stream.mute = opts.mute || false
  stream.audioEffect = opts.audioEffect || null
  stream.index = opts.index == null ? self._streams.length : opts.index

  // If it is the same MediaStream, we can reuse our video element (and ignore sound)
  var videoElement = null
  for (var i = 0; i < self._streams.length; i++) {
    if (self._streams[i].id === mediaStream.id) {
      videoElement = self._streams[i].element
    }
  }

  if (!videoElement) {
    videoElement = document.createElement('video')
    videoElement.autoplay = true
    videoElement.muted = true
    videoElement.srcObject = mediaStream

    if (!stream.mute) {
      stream.audioSource = self._audioCtx.createMediaStreamSource(mediaStream)
      stream.audioOutput = self._audioCtx.createGain() // Intermediate gain node
      stream.audioOutput.gain.value = 1
      if (stream.audioEffect) {
        stream.audioEffect(stream.audioSource, stream.audioOutput)
      } else {
        stream.audioSource.connect(stream.audioOutput) // Default is direct connect
      }
      stream.audioOutput.connect(self._audioDestination)
    }
  }

  stream.element = videoElement
  stream.id = mediaStream.id || null
  self._streams.splice(stream.index, 0, stream)
}

VideoStreamMerger.prototype.removeStream = function (mediaStream) {
  var self = this

  if (typeof mediaStream === 'string') {
    mediaStream = {
      id: mediaStream
    }
  }

  for (var i = 0; i < self._streams.length; i++) {
    if (mediaStream.id === self._streams[i].id) {
      if (self._streams[i].audioSource) {
        self._streams[i].audioSource = null
      }
      if (self._streams[i].audioOutput) {
        self._streams[i].audioOutput.disconnect(self._audioDestination)
        self._streams[i].audioOutput = null
      }

      self._streams[i] = null
      self._streams.splice(i, 1)
      i--
    }
  }
}

VideoStreamMerger.prototype._addData = function (key, opts) {
  var self = this

  opts = opts || {}
  var stream = {}

  stream.isData = true
  stream.draw = opts.draw || null
  stream.audioEffect = opts.audioEffect || null
  stream.id = key
  stream.element = null
  stream.index = opts.index == null ? self._streams.length : opts.index

  if (stream.audioEffect) {
    stream.audioOutput = self._audioCtx.createGain() // Intermediate gain node
    stream.audioOutput.gain.value = 1
    stream.audioEffect(null, stream.audioOutput)
    stream.audioOutput.connect(self._audioDestination)
  }

  self._streams.splice(stream.index, 0, stream)
}

VideoStreamMerger.prototype.start = function () {
  var self = this

  self.started = true
  window.requestAnimationFrame(self._draw.bind(self))

  // Add video
  self.result = self._canvas.captureStream(self.fps)

  // Remove "dead" audio track
  var deadTrack = self.result.getAudioTracks()[0]
  if (deadTrack) self.result.removeTrack(deadTrack)

  // Add audio
  var audioTracks = self._audioDestination.stream.getAudioTracks()
  self.result.addTrack(audioTracks[0])
}

VideoStreamMerger.prototype._draw = function () {
  var self = this
  if (!self.started) return

  var awaiting = self._streams.length
  function done () {
    awaiting--
    if (awaiting <= 0) window.requestAnimationFrame(self._draw.bind(self))
  }

  self._ctx.clearRect(0, 0, self.width, self.height)
  self._streams.forEach(function (video) {
    if (video.draw) { // custom frame transform
      video.draw(self._ctx, video.element, done)
    } else if (!video.isData) {
      self._ctx.drawImage(video.element, video.x, video.y, video.width, video.height)
      done()
    } else {
      done()
    }
  })

  if (self._streams.length === 0) done()
}

VideoStreamMerger.prototype.destroy = function () {
  var self = this

  self.started = false

  self._canvas = null
  self._ctx = null
  self._streams = []
  self._audioCtx.close()
  self._audioCtx = null
  self._audioDestination = null

  self.result.getTracks().forEach(function (t) {
    t.stop()
  })
  self.result = null
}

module.exports = VideoStreamMerger
<commit_msg>fix typo<commit_after>/* globals window */

module.exports = VideoStreamMerger

function VideoStreamMerger (opts) {
  var self = this
  if (!(self instanceof VideoStreamMerger)) return new VideoStreamMerger(opts)

  opts = opts || {}

  var AudioContext = window.AudioContext || window.webkitAudioContext
  var audioSupport = !!(AudioContext && (self._audioCtx = (opts.audioContext || new AudioContext())).createMediaStreamDestination)
  var canvasSupport = !!document.createElement('canvas').captureStream
  var supported = audioSupport && canvasSupport
  if (!supported) {
    throw new Error('Unsupported browser')
  }
  self.width = opts.width || 400
  self.height = opts.height || 300
  self.fps = opts.fps || 25

  // Hidden canvas element for merging
  self._canvas = document.createElement('canvas')
  self._canvas.setAttribute('width', self.width)
  self._canvas.setAttribute('height', self.height)
  self._canvas.setAttribute('style', 'position:fixed; left: 110%; pointer-events: none') // Push off screen
  self._ctx = self._canvas.getContext('2d')

  self._streams = []

  self._audioDestination = self._audioCtx.createMediaStreamDestination()

  self._setupConstantNode() // HACK for wowza #7, #10

  self.started = false
  self.result = null

  self._backgroundAudioHack()
}

VideoStreamMerger.prototype._backgroundAudioHack = function () {
  var self = this

  // stop browser from throttling timers by playing almost-silent audio
  var source = self._audioCtx.createConstantSource()
  var gainNode = ctx.createGain()
  gainNode.gain.value = 0.001 // required to prevent popping on start
  source.connect(gainNode)
  gainNode.connect(ctx.destination)
  source.start()
}

VideoStreamMerger.prototype._setupConstantNode = function () {
  var self = this

  var constantAudioNode = self._audioCtx.createConstantSource()
  constantAudioNode.start()

  var gain = self._audioCtx.createGain() // gain node prevents quality drop
  gain.gain.value = 0

  constantAudioNode.connect(gain)
  gain.connect(self._audioDestination)
}

VideoStreamMerger.prototype.updateIndex = function (mediaStream, index) {
  var self = this

  if (typeof mediaStream === 'string') {
    mediaStream = {
      id: mediaStream
    }
  }

  index = index == null ? self._streams.length : index

  for (var i = 0; i < self._streams.length; i++) {
    if (mediaStream.id === self._streams[i].id) {
      var stream = self._streams.splice(i, 1)[0]
      stream.index = index
      self._streams.splice(stream.index, 0, stream)
    }
  }
}

VideoStreamMerger.prototype.addStream = function (mediaStream, opts) {
  var self = this

  if (typeof mediaStream === 'string') {
    return self._addData(mediaStream, opts)
  }

  opts = opts || {}
  var stream = {}

  stream.isData = false
  stream.x = opts.x || 0
  stream.y = opts.y || 0
  stream.width = opts.width || self.width
  stream.height = opts.height || self.height
  stream.draw = opts.draw || null
  stream.mute = opts.mute || false
  stream.audioEffect = opts.audioEffect || null
  stream.index = opts.index == null ? self._streams.length : opts.index

  // If it is the same MediaStream, we can reuse our video element (and ignore sound)
  var videoElement = null
  for (var i = 0; i < self._streams.length; i++) {
    if (self._streams[i].id === mediaStream.id) {
      videoElement = self._streams[i].element
    }
  }

  if (!videoElement) {
    videoElement = document.createElement('video')
    videoElement.autoplay = true
    videoElement.muted = true
    videoElement.srcObject = mediaStream

    if (!stream.mute) {
      stream.audioSource = self._audioCtx.createMediaStreamSource(mediaStream)
      stream.audioOutput = self._audioCtx.createGain() // Intermediate gain node
      stream.audioOutput.gain.value = 1
      if (stream.audioEffect) {
        stream.audioEffect(stream.audioSource, stream.audioOutput)
      } else {
        stream.audioSource.connect(stream.audioOutput) // Default is direct connect
      }
      stream.audioOutput.connect(self._audioDestination)
    }
  }

  stream.element = videoElement
  stream.id = mediaStream.id || null
  self._streams.splice(stream.index, 0, stream)
}

VideoStreamMerger.prototype.removeStream = function (mediaStream) {
  var self = this

  if (typeof mediaStream === 'string') {
    mediaStream = {
      id: mediaStream
    }
  }

  for (var i = 0; i < self._streams.length; i++) {
    if (mediaStream.id === self._streams[i].id) {
      if (self._streams[i].audioSource) {
        self._streams[i].audioSource = null
      }
      if (self._streams[i].audioOutput) {
        self._streams[i].audioOutput.disconnect(self._audioDestination)
        self._streams[i].audioOutput = null
      }

      self._streams[i] = null
      self._streams.splice(i, 1)
      i--
    }
  }
}

VideoStreamMerger.prototype._addData = function (key, opts) {
  var self = this

  opts = opts || {}
  var stream = {}

  stream.isData = true
  stream.draw = opts.draw || null
  stream.audioEffect = opts.audioEffect || null
  stream.id = key
  stream.element = null
  stream.index = opts.index == null ? self._streams.length : opts.index

  if (stream.audioEffect) {
    stream.audioOutput = self._audioCtx.createGain() // Intermediate gain node
    stream.audioOutput.gain.value = 1
    stream.audioEffect(null, stream.audioOutput)
    stream.audioOutput.connect(self._audioDestination)
  }

  self._streams.splice(stream.index, 0, stream)
}

VideoStreamMerger.prototype.start = function () {
  var self = this

  self.started = true
  window.requestAnimationFrame(self._draw.bind(self))

  // Add video
  self.result = self._canvas.captureStream(self.fps)

  // Remove "dead" audio track
  var deadTrack = self.result.getAudioTracks()[0]
  if (deadTrack) self.result.removeTrack(deadTrack)

  // Add audio
  var audioTracks = self._audioDestination.stream.getAudioTracks()
  self.result.addTrack(audioTracks[0])
}

VideoStreamMerger.prototype._draw = function () {
  var self = this
  if (!self.started) return

  var awaiting = self._streams.length
  function done () {
    awaiting--
    if (awaiting <= 0) window.requestAnimationFrame(self._draw.bind(self))
  }

  self._ctx.clearRect(0, 0, self.width, self.height)
  self._streams.forEach(function (video) {
    if (video.draw) { // custom frame transform
      video.draw(self._ctx, video.element, done)
    } else if (!video.isData) {
      self._ctx.drawImage(video.element, video.x, video.y, video.width, video.height)
      done()
    } else {
      done()
    }
  })

  if (self._streams.length === 0) done()
}

VideoStreamMerger.prototype.destroy = function () {
  var self = this

  self.started = false

  self._canvas = null
  self._ctx = null
  self._streams = []
  self._audioCtx.close()
  self._audioCtx = null
  self._audioDestination = null

  self.result.getTracks().forEach(function (t) {
    t.stop()
  })
  self.result = null
}

module.exports = VideoStreamMerger
<|endoftext|>


<commit_before>export fetch from './components/FetchContainer';
export fetchAsyncState from './utilities/fetchAsyncState';
export FetchProvider from './components/FetchProvider';
<commit_msg>:joy: Export React Router middleware<commit_after>export fetch from './components/FetchContainer';
export fetchAsyncState from './utilities/fetchAsyncState';
export FetchProvider from './components/FetchProvider';
export useFetch from './utilities/useFetch';
<|endoftext|>


<commit_before>const entity = require("./domain/entity");
const fsFileAdapter = require("./infrastructure/fsFileAdapter");
const azureBlobStorageAdapter = require("./infrastructure/azureBlobStorageFileAdapter");
const JSONSerializer = require("./middleware/JSONSerializer");

/**
 * @type {{ntt: entity, adapters: {fs: fsFileAdapter}}}
 */
const ntt =  {
  ntt: (adapter, serializer = JSONSerializer) => {
    return entity(adapter, serializer);
  },
  adapters: {
    fs: fsFileAdapter,
    azure: azureBlobStorageAdapter
  }
};

module.exports = ntt;<commit_msg>Injection of stuff in the index<commit_after>const entityFactoryProvider = require("./domain/entity");
const resourceProviderFactory = require("./domain/resource");
const fsFileAdapter = require("./infrastructure/fsFileAdapter");
const azureBlobStorageAdapter = require("./infrastructure/azureBlobStorageFileAdapter");
const JSONSerializer = require("./middleware/JSONSerializer");

/**
 * @type {{ntt: entity, adapters: {fs: fsFileAdapter}}}
 */
const ntt =  {
  ntt: (adapter, serializer = JSONSerializer) => {
    const entityFactory = entityFactoryProvider(serializer, resourceProviderFactory);
    return entityFactory.create(adapter);
  },
  adapters: {
    fs: fsFileAdapter,
    azure: azureBlobStorageAdapter
  }
};

module.exports = ntt;<|endoftext|>


<commit_before>import './index.css';
import * as ChatWebAPIUtils from './utils/ChatWebAPIUtils';
import * as Actions from './actions/';
import App from './App'
import MessageStore from './stores/MessageStore';
import React from 'react';
import ReactDOM from 'react-dom';
import UserPreferencesStore from './stores/UserPreferencesStore';
// Internationalization
import en from 'react-intl/locale-data/en';
import fr from 'react-intl/locale-data/fr';
import es from 'react-intl/locale-data/es';
import de from 'react-intl/locale-data/de';
import ru from 'react-intl/locale-data/ru';
import { IntlProvider } from 'react-intl';
import { addLocaleData } from 'react-intl';
import { BrowserRouter as Router } from 'react-router-dom';

addLocaleData([...en, ...fr, ...es, ...de, ...ru]);

ChatWebAPIUtils.getSettings();
ChatWebAPIUtils.getLocation();
ChatWebAPIUtils.getHistory();
ChatWebAPIUtils.getAllMessages();

let defaults = UserPreferencesStore.getPreferences();
let defaultPrefLanguage = defaults.PrefLanguage;

window.speechSynthesis.onvoiceschanged = function () {
	if (!MessageStore.getTTSInitStatus()) {
		var speechSynthesisVoices = speechSynthesis.getVoices();
		Actions.getTTSLangText(speechSynthesisVoices);
		Actions.initialiseTTSVoices(speechSynthesisVoices);
	}
};

ReactDOM.render(
	<IntlProvider locale={defaultPrefLanguage}>
		<Router>
				<App />
		</Router>
	</IntlProvider>,
	document.getElementById('root')
);
<commit_msg>Update index.js<commit_after>import './index.css';
import * as ChatWebAPIUtils from './utils/ChatWebAPIUtils';
import * as Actions from './actions/';
import App from './App'
import MessageStore from './stores/MessageStore';
import React from 'react';
import ReactDOM from 'react-dom';
import UserPreferencesStore from './stores/UserPreferencesStore';
// Internationalization
import en from 'react-intl/locale-data/en';
import fr from 'react-intl/locale-data/fr';
import es from 'react-intl/locale-data/es';
import de from 'react-intl/locale-data/de';
import ru from 'react-intl/locale-data/ru';
import { IntlProvider } from 'react-intl';
import { addLocaleData } from 'react-intl';
import { BrowserRouter as Router } from 'react-router-dom';

addLocaleData([...en, ...fr, ...es, ...de, ...ru]);

ChatWebAPIUtils.getSettings();
ChatWebAPIUtils.getLocation();
ChatWebAPIUtils.getHistory();
ChatWebAPIUtils.getAllMessages();

let defaults = UserPreferencesStore.getPreferences();
let defaultPrefLanguage = defaults.PrefLanguage;

window.speechSynthesis.onvoiceschanged = function () {
	if (!MessageStore.getTTSInitStatus()) {
		var speechSynthesisVoices = speechSynthesis.getVoices();
		Actions.getTTSLangText(speechSynthesisVoices);
		Actions.initialiseTTSVoices(speechSynthesisVoices);
	}
var recognition = new SpeechRecognition();

recognition.onsoundend = function() {
  var color = event.results[0][0].transcript;
  diagnostic.textContent = 'Result received: ' + color + '.';
  bg.style.backgroundColor = color;
}

};

ReactDOM.render(
	<IntlProvider locale={defaultPrefLanguage}>
		<Router>
				<App />
		</Router>
	</IntlProvider>,
	document.getElementById('root')
);
<|endoftext|>


<commit_before>/* global window */

'use strict';

var React = require('react');
var assign = require('object-assign');
var omit = require('lodash.omit');
var mediaQuery = require('./mediaQuery');
var toQuery = require('./toQuery');
var matchMedia = typeof window != 'undefined' ? window.matchMedia : null;

var defaultTypes = {
  component: React.PropTypes.func,
  query: React.PropTypes.string
};
var mediaKeys = Object.keys(mediaQuery.all);
var types = assign(defaultTypes, mediaQuery.all);
var excludedQueryKeys = Object.keys(types);
var excludedPropKeys = excludedQueryKeys.concat(mediaKeys);

var mq = React.createClass({
  displayName: 'MediaQuery',
  propTypes: types,

  getDefaultProps: function(){
    return {
      component: React.DOM.div
    };
  },

  getInitialState: function(){
    return {
      matches: false
    };
  },

  componentWillMount: function(){
    this.updateQuery(this.props);
  },

  componentWillReceiveProps: function(props){
    this.updateQuery(props);
  },

  updateQuery: function(props){
    if (props.query) {
      this.query = props.query;
    } else {
      this.query = toQuery(omit(props, excludedQueryKeys));
    }

    if (!this.query) {
      throw new Error('Invalid or missing MediaQuery!');
    }
    this._mql = matchMedia(this.query);
    this._mql.addListener(this.updateMatches);
    this.updateMatches();
  },

  componentWillUnmount: function(){
    this._mql.removeListener(this.updateMatches);
  },

  updateMatches: function(){
    if (this._mql.matches === this.state.matches) {
      return;
    }
    this.setState({
      matches: this._mql.matches
    });
  },

  render: function(){
    if (this.state.matches === false) {
      return null;
    }
    var props = omit(this.props, excludedPropKeys);
    return this.props.component(props, this.props.children);
  }
});

module.exports = mq;
<commit_msg>fix linter<commit_after>/* global window */

'use strict';

var React = require('react');
var assign = require('object-assign');
var omit = require('lodash.omit');
var mediaQuery = require('./mediaQuery');
var toQuery = require('./toQuery');
var matchMedia = typeof window !== 'undefined' ? window.matchMedia : null;

var defaultTypes = {
  component: React.PropTypes.func,
  query: React.PropTypes.string
};
var mediaKeys = Object.keys(mediaQuery.all);
var types = assign(defaultTypes, mediaQuery.all);
var excludedQueryKeys = Object.keys(types);
var excludedPropKeys = excludedQueryKeys.concat(mediaKeys);

var mq = React.createClass({
  displayName: 'MediaQuery',
  propTypes: types,

  getDefaultProps: function(){
    return {
      component: React.DOM.div
    };
  },

  getInitialState: function(){
    return {
      matches: false
    };
  },

  componentWillMount: function(){
    this.updateQuery(this.props);
  },

  componentWillReceiveProps: function(props){
    this.updateQuery(props);
  },

  updateQuery: function(props){
    if (props.query) {
      this.query = props.query;
    } else {
      this.query = toQuery(omit(props, excludedQueryKeys));
    }

    if (!this.query) {
      throw new Error('Invalid or missing MediaQuery!');
    }
    this._mql = matchMedia(this.query);
    this._mql.addListener(this.updateMatches);
    this.updateMatches();
  },

  componentWillUnmount: function(){
    this._mql.removeListener(this.updateMatches);
  },

  updateMatches: function(){
    if (this._mql.matches === this.state.matches) {
      return;
    }
    this.setState({
      matches: this._mql.matches
    });
  },

  render: function(){
    if (this.state.matches === false) {
      return null;
    }
    var props = omit(this.props, excludedPropKeys);
    return this.props.component(props, this.props.children);
  }
});

module.exports = mq;
<|endoftext|>