<commit_before>"use strict";

var starterkit_manager = function (config) {
  var path = require('path'),
    fetch = require('node-fetch'),
    fs = require('fs-extra'),
    util = require('./utilities'),
    paths = config.paths;

  function loadStarterKit(starterkitName, clean) {
    try {
      var kitPath = path.resolve(
        path.join(process.cwd(), 'node_modules', starterkitName, config.starterkitSubDir)
      );
      console.log('Attempting to load starterkit from', kitPath);
      try {
        var kitDirStats = fs.statSync(kitPath);
      } catch (ex) {
        util.logRed(starterkitName + ' not found, please use npm to install it first.');
        util.logRed(starterkitName + ' not loaded.');
        return;
      }
      var kitPathDirExists = kitDirStats.isDirectory();
      if (kitPathDirExists) {

        if (clean) {
          console.log('Deleting contents of', paths.source.root, 'prior to starterkit load.');
          util.emptyDirectory(paths.source.root);
        } else {
          console.log('Overwriting contents of', paths.source.root, 'during starterkit load.');
        }

        fs.copy(kitPath, paths.source.root, function (ex) {
          if (ex) {
            console.error(ex);
          }
          util.logGreen('starterkit ' + starterkitName + ' loaded successfully.');
        });
      }
    } catch (ex) {
      console.log(ex);
    }
  }

  /**
   * @func listStarterkits
   * @desc Fetches starterkit repos from GH API that contain 'starterkit' in their name for the user 'pattern-lab'
   * @returns {Promise} Returns an Array<{name,url}> for the starterkit repos
   */
  function listStarterkits() {
    return fetch('https://api.github.com/search/repositories?q=starterkit+in:name+user:pattern-lab&sort=stars&order=desc', {
      method: 'GET',
      headers: {
        'Accept': 'application/json'
      }
    }).then(function (res) {
      var contentType = res.headers.get('content-type');
      if (contentType && contentType.indexOf('application/json') === -1) {
        throw new TypeError("StarterkitManager->listStarterkits: Not valid JSON");
      }
      return res.json();
    }).then(function (json) {
      if (!json.items || !Array.isArray(json.items)) {
        return false;
      }
      return json.items
        .map(function (repo) {
          return {name: repo.name, url: repo.html_url};
        });
    }).catch(function (err) {
      console.error(err);
      return false;
    });
  }

  function packStarterkit() {

  }

  function detectStarterKits() {
    var node_modules_path = path.join(process.cwd(), 'node_modules');
    var npm_modules = fs.readdirSync(node_modules_path).filter(function (dir) {
      var module_path = path.join(process.cwd(), 'node_modules', dir);
      return fs.statSync(module_path).isDirectory() && dir.indexOf('starterkit-') === 0;
    });
    return npm_modules;
  }

  return {
    load_starterkit: function (starterkitName, clean) {
      loadStarterKit(starterkitName, clean);
    },
    list_starterkits: function () {
      return listStarterkits();
    },
    pack_starterkit: function () {
      packStarterkit();
    },
    detect_starterkits: function () {
      return detectStarterKits();
    }
  };

};

module.exports = starterkit_manager;
<commit_msg>#490 Add proper javadoc comments to core/lib/starterkit_manager.js<commit_after>"use strict";

var starterkit_manager = function (config) {
  var path = require('path'),
    fetch = require('node-fetch'),
    fs = require('fs-extra'),
    util = require('./utilities'),
    paths = config.paths;

  /**
   * Loads npm module identified by the starterkitName parameter.
   *
   * @param starterkitName {string} Kit name
   * @param clean {boolean}  Indicates if the directory should be cleaned before loading
  */
  function loadStarterKit(starterkitName, clean) {
    try {
      var kitPath = path.resolve(
        path.join(process.cwd(), 'node_modules', starterkitName, config.starterkitSubDir)
      );
      console.log('Attempting to load starterkit from', kitPath);
      try {
        var kitDirStats = fs.statSync(kitPath);
      } catch (ex) {
        util.logRed(starterkitName + ' not found, please use npm to install it first.');
        util.logRed(starterkitName + ' not loaded.');
        return;
      }
      var kitPathDirExists = kitDirStats.isDirectory();
      if (kitPathDirExists) {

        if (clean) {
          console.log('Deleting contents of', paths.source.root, 'prior to starterkit load.');
          util.emptyDirectory(paths.source.root);
        } else {
          console.log('Overwriting contents of', paths.source.root, 'during starterkit load.');
        }

        fs.copy(kitPath, paths.source.root, function (ex) {
          if (ex) {
            console.error(ex);
          }
          util.logGreen('starterkit ' + starterkitName + ' loaded successfully.');
        });
      }
    } catch (ex) {
      console.log(ex);
    }
  }

  /**
   * Fetches starterkit repos from GH API that contain 'starterkit' in their name for the user 'pattern-lab'
   *
   * @return {Promise} Returns an Array<{name,url}> for the starterkit repos
   */
  function listStarterkits() {
    return fetch('https://api.github.com/search/repositories?q=starterkit+in:name+user:pattern-lab&sort=stars&order=desc', {
      method: 'GET',
      headers: {
        'Accept': 'application/json'
      }
    }).then(function (res) {
      var contentType = res.headers.get('content-type');
      if (contentType && contentType.indexOf('application/json') === -1) {
        throw new TypeError("StarterkitManager->listStarterkits: Not valid JSON");
      }
      return res.json();
    }).then(function (json) {
      if (!json.items || !Array.isArray(json.items)) {
        return false;
      }
      return json.items
        .map(function (repo) {
          return {name: repo.name, url: repo.html_url};
        });
    }).catch(function (err) {
      console.error(err);
      return false;
    });
  }

  function packStarterkit() {

  }

  /**
   * Detects installed starter kits
   *
   * @return {array} List of starter kits installed
   */
  function detectStarterKits() {
    var node_modules_path = path.join(process.cwd(), 'node_modules');
    var npm_modules = fs.readdirSync(node_modules_path).filter(function (dir) {
      var module_path = path.join(process.cwd(), 'node_modules', dir);
      return fs.statSync(module_path).isDirectory() && dir.indexOf('starterkit-') === 0;
    });
    return npm_modules;
  }

  return {
    load_starterkit: function (starterkitName, clean) {
      loadStarterKit(starterkitName, clean);
    },
    list_starterkits: function () {
      return listStarterkits();
    },
    pack_starterkit: function () {
      packStarterkit();
    },
    detect_starterkits: function () {
      return detectStarterKits();
    }
  };

};

module.exports = starterkit_manager;
<|endoftext|>


<commit_before>const _ = require('lodash');

/**
 * @typedef {object} ILogger
 * @prop {(x: any) => void} error
 * @prop {(x: any) => void} info
 * @prop {(x: any) => void} warn
 */
module.exports = class StripeMigrations {
    /**
     * StripeMigrations
     *
     * @param {object} params
     *
     * @param {ILogger} params.logger
     * @param {any} params.StripeCustomerSubscription
     * @param {any} params.StripeProduct
     * @param {any} params.StripePrice
     * @param {any} params.Product
     * @param {any} params.stripeAPIService
     */
    constructor({
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        stripeAPIService,
        logger
    }) {
        this._logging = logger;
        this._StripeCustomerSubscription = StripeCustomerSubscription;
        this._StripeProduct = StripeProduct;
        this._StripePrice = StripePrice;
        this._Product = Product;
        this._StripeAPIService = stripeAPIService;
    }

    async populateProductsAndPrices() {
        const subscriptionModels = await this._StripeCustomerSubscription.findAll();
        const priceModels = await this._StripePrice.findAll();
        const productModels = await this._StripeProduct.findAll();
        const subscriptions = subscriptionModels.toJSON();
        const prices = priceModels.toJSON();
        const products = productModels.toJSON();
        const {data} = await this._Product.findPage({
            limit: 1
        });
        const defaultProduct = data[0] && data[0].toJSON();

        /** Only run when -
         * No rows in stripe_products,
         * No rows in stripe_prices,
         * One or more rows in members_stripe_customers_subscriptions
         * */
        if (subscriptions.length > 0 && products.length === 0 && prices.length === 0 && defaultProduct) {
            try {
                this._logging.info(`Populating products and prices for existing stripe customers`);
                const uniquePlans = _.uniq(subscriptions.map(d => _.get(d, 'plan.id')));

                let stripePlans = [];
                for (const plan of uniquePlans) {
                    const stripePlan = await this._StripeAPIService.getPlan(plan, {
                        expand: ['product']
                    });
                    stripePlans.push(stripePlan);
                }
                this._logging.info(`Adding ${stripePlans.length} plans from Stripe`);
                for (const stripePlan of stripePlans) {
                    const stripeProduct = stripePlan.product;

                    await this._StripeProduct.upsert({
                        product_id: defaultProduct.id,
                        stripe_product_id: stripeProduct.id
                    });

                    await this._StripePrice.add({
                        stripe_price_id: stripePlan.id,
                        stripe_product_id: stripeProduct.id,
                        active: stripePlan.active,
                        nickname: stripePlan.nickname,
                        currency: stripePlan.currency,
                        amount: stripePlan.amount,
                        type: 'recurring',
                        interval: stripePlan.interval
                    });
                }
            } catch (e) {
                this._logging.error(`Failed to populate products/prices from stripe`);
                this._logging.error(e);
            }
        }
    }
};
<commit_msg>Ignored missing plans on Stripe for update<commit_after>const _ = require('lodash');

/**
 * @typedef {object} ILogger
 * @prop {(x: any) => void} error
 * @prop {(x: any) => void} info
 * @prop {(x: any) => void} warn
 */
module.exports = class StripeMigrations {
    /**
     * StripeMigrations
     *
     * @param {object} params
     *
     * @param {ILogger} params.logger
     * @param {any} params.StripeCustomerSubscription
     * @param {any} params.StripeProduct
     * @param {any} params.StripePrice
     * @param {any} params.Product
     * @param {any} params.stripeAPIService
     */
    constructor({
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        stripeAPIService,
        logger
    }) {
        this._logging = logger;
        this._StripeCustomerSubscription = StripeCustomerSubscription;
        this._StripeProduct = StripeProduct;
        this._StripePrice = StripePrice;
        this._Product = Product;
        this._StripeAPIService = stripeAPIService;
    }

    async populateProductsAndPrices() {
        const subscriptionModels = await this._StripeCustomerSubscription.findAll();
        const priceModels = await this._StripePrice.findAll();
        const productModels = await this._StripeProduct.findAll();
        const subscriptions = subscriptionModels.toJSON();
        const prices = priceModels.toJSON();
        const products = productModels.toJSON();
        const {data} = await this._Product.findPage({
            limit: 1
        });
        const defaultProduct = data[0] && data[0].toJSON();

        /** Only run when -
         * No rows in stripe_products,
         * No rows in stripe_prices,
         * One or more rows in members_stripe_customers_subscriptions
         * */
        if (subscriptions.length > 0 && products.length === 0 && prices.length === 0 && defaultProduct) {
            try {
                this._logging.info(`Populating products and prices for existing stripe customers`);
                const uniquePlans = _.uniq(subscriptions.map(d => _.get(d, 'plan.id')));

                let stripePlans = [];
                for (const plan of uniquePlans) {
                    try {
                        const stripePlan = await this._StripeAPIService.getPlan(plan, {
                            expand: ['product']
                        });
                        stripePlans.push(stripePlan);
                    } catch (err) {
                        if (err && err.statusCode === 404) {
                            this._logging.warn(`Plan ${plan} not found on Stripe - ignoring`);
                        } else {
                            throw err;
                        }
                    }
                }
                this._logging.info(`Adding ${stripePlans.length} plans from Stripe`);
                for (const stripePlan of stripePlans) {
                    const stripeProduct = stripePlan.product;

                    await this._StripeProduct.upsert({
                        product_id: defaultProduct.id,
                        stripe_product_id: stripeProduct.id
                    });

                    await this._StripePrice.add({
                        stripe_price_id: stripePlan.id,
                        stripe_product_id: stripeProduct.id,
                        active: stripePlan.active,
                        nickname: stripePlan.nickname,
                        currency: stripePlan.currency,
                        amount: stripePlan.amount,
                        type: 'recurring',
                        interval: stripePlan.interval
                    });
                }
            } catch (e) {
                this._logging.error(`Failed to populate products/prices from stripe`);
                this._logging.error(e);
            }
        }
    }
};
<|endoftext|>


<commit_before>const _ = require('lodash');

/**
 * @typedef {object} ILogger
 * @prop {(x: any) => void} error
 * @prop {(x: any) => void} info
 * @prop {(x: any) => void} warn
 */
module.exports = class StripeMigrations {
    /**
     * StripeMigrations
     *
     * @param {object} params
     *
     * @param {ILogger} params.logger
     * @param {any} params.StripeCustomerSubscription
     * @param {any} params.StripeProduct
     * @param {any} params.StripePrice
     * @param {any} params.Product
     * @param {any} params.Settings
     * @param {import('../services/stripe-api')} params.stripeAPIService
     */
    constructor({
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        Settings,
        stripeAPIService,
        logger
    }) {
        this._logging = logger;
        this._StripeCustomerSubscription = StripeCustomerSubscription;
        this._StripeProduct = StripeProduct;
        this._StripePrice = StripePrice;
        this._Product = Product;
        this._Settings = Settings;
        this._stripeAPIService = stripeAPIService;
    }

    async populateProductsAndPrices() {
        const subscriptionModels = await this._StripeCustomerSubscription.findAll();
        const priceModels = await this._StripePrice.findAll();
        const productModels = await this._StripeProduct.findAll();
        const subscriptions = subscriptionModels.toJSON();
        const prices = priceModels.toJSON();
        const products = productModels.toJSON();
        const {data} = await this._Product.findPage({
            limit: 1
        });
        const defaultProduct = data[0] && data[0].toJSON();

        /** Only run when -
         * No rows in stripe_products,
         * No rows in stripe_prices,
         * One or more rows in members_stripe_customers_subscriptions
         * */
        if (subscriptions.length > 0 && products.length === 0 && prices.length === 0 && defaultProduct) {
            try {
                this._logging.info(`Populating products and prices for existing stripe customers`);
                const uniquePlans = _.uniq(subscriptions.map(d => _.get(d, 'plan.id')));

                let stripePlans = [];
                for (const plan of uniquePlans) {
                    try {
                        const stripePlan = await this._stripeAPIService.getPlan(plan, {
                            expand: ['product']
                        });
                        stripePlans.push(stripePlan);
                    } catch (err) {
                        if (err && err.statusCode === 404) {
                            this._logging.warn(`Plan ${plan} not found on Stripe - ignoring`);
                        } else {
                            throw err;
                        }
                    }
                }
                this._logging.info(`Adding ${stripePlans.length} plans from Stripe`);
                for (const stripePlan of stripePlans) {
                    const stripeProduct = stripePlan.product;

                    await this._StripeProduct.upsert({
                        product_id: defaultProduct.id,
                        stripe_product_id: stripeProduct.id
                    });

                    await this._StripePrice.add({
                        stripe_price_id: stripePlan.id,
                        stripe_product_id: stripeProduct.id,
                        active: stripePlan.active,
                        nickname: stripePlan.nickname,
                        currency: stripePlan.currency,
                        amount: stripePlan.amount,
                        type: 'recurring',
                        interval: stripePlan.interval
                    });
                }
            } catch (e) {
                this._logging.error(`Failed to populate products/prices from stripe`);
                this._logging.error(e);
            }
        }
    }

    async findPriceByPlan(plan) {
        const currency = plan.currency ? plan.currency.toLowerCase() : 'usd';
        const amount = Number.isInteger(plan.amount) ? plan.amount : parseInt(plan.amount);
        const interval = plan.interval;

        const price = await this._StripePrice.findOne({
            currency,
            amount,
            interval
        });

        return price;
    }

    async populateStripePricesFromStripePlansSetting(plans) {
        if (!plans) {
            this._logging.info('Skipping stripe_plans -> stripe_prices migration');
            return;
        }
        let defaultStripeProduct;
        const stripeProductsPage = await this._StripeProduct.findPage({limit: 1});
        defaultStripeProduct = stripeProductsPage.data[0];

        if (!defaultStripeProduct) {
            this._logging.info('Could not find Stripe Product - creating one');
            const productsPage = await this._Product.findPage({limit: 1});
            const defaultProduct = productsPage.data[0];
            const stripeProduct = await this._stripeAPIService.createProduct({
                name: defaultProduct.get('name')
            });
            if (!defaultProduct) {
                this._logging.error('Could not find Product - skipping stripe_plans -> stripe_prices migration');
                return;
            }
            defaultStripeProduct = await this._StripeProduct.add({
                product_id: defaultProduct.id,
                stripe_product_id: stripeProduct.id
            });
        }

        for (const plan of plans) {
            const price = await this.findPriceByPlan(plan);

            if (!price) {
                this._logging.info(`Could not find Stripe Price ${JSON.stringify(plan)}`);

                try {
                    this._logging.info(`Creating Stripe Price ${JSON.stringify(plan)}`);
                    const price = await this._stripeAPIService.createPrice({
                        currency: plan.currency,
                        amount: plan.amount,
                        nickname: plan.name,
                        interval: plan.interval,
                        active: true,
                        type: 'recurring',
                        product: defaultStripeProduct.get('stripe_product_id')
                    });

                    await this._StripePrice.add({
                        stripe_price_id: price.id,
                        stripe_product_id: defaultStripeProduct.get('stripe_product_id'),
                        active: price.active,
                        nickname: price.nickname,
                        currency: price.currency,
                        amount: price.unit_amount,
                        type: 'recurring',
                        interval: price.recurring.interval
                    });
                } catch (err) {
                    this._logging.error({err, message: 'Adding price failed'});
                }
            }
        }
    }

    async updatePortalPlansSetting(plans) {
        this._logging.info('Migrating portal_plans setting from names to ids');
        const portalPlansSetting = await this._Settings.findOne({key: 'portal_plans'});

        let portalPlans;
        try {
            portalPlans = JSON.parse(portalPlansSetting.get('value'));
        } catch (err) {
            this._logging.error({
                message: 'Could not parse portal_plans setting, skipping migration',
                err
            });
            return;
        }

        const containsOldValues = !!portalPlans.find((plan) => {
            return ['monthly', 'yearly'].includes(plan);
        });

        if (!containsOldValues) {
            this._logging.info('Could not find names in portal_plans setting, skipping migration');
            return;
        }

        const newPortalPlans = await portalPlans.reduce(async (newPortalPlansPromise, plan) => {
            let newPlan = plan;
            if (plan === 'monthly') {
                const monthlyPlan = plans.find((plan) => {
                    return plan.name === 'Monthly';
                });
                if (!monthlyPlan) {
                    return newPortalPlansPromise;
                }
                const price = await this.findPriceByPlan(monthlyPlan);
                newPlan = price.id;
            }
            if (plan === 'yearly') {
                const yearlyPlan = plans.find((plan) => {
                    return plan.name === 'Yearly';
                });
                if (!yearlyPlan) {
                    return newPortalPlansPromise;
                }
                const price = await this.findPriceByPlan(yearlyPlan);
                newPlan = price.id;
            }
            const newPortalPlans = await newPortalPlansPromise;
            return newPortalPlans.concat(newPlan);
        }, []);

        this._logging.info(`Updating portal_plans setting to ${JSON.stringify(newPortalPlans)}`);
        await this._Settings.edit({
            key: 'portal_plans',
            value: JSON.stringify(newPortalPlans)
        }, {
            id: portalPlansSetting.id
        });
    }

    async populateMembersMonthlyPriceIdSettings() {
        this._logging.info('Populating members_monthly_price_id from stripe_plans');
        const monthlyPriceId = await this._Settings.findOne({key: 'members_monthly_price_id'});

        if (monthlyPriceId.get('value')) {
            this._logging.info('Skipping population of members_monthly_price_id, already populated');
            return;
        }

        const stripePlans = await this._Settings.findOne({key: 'stripe_plans'});
        let plans;
        try {
            plans = JSON.parse(stripePlans.get('value'));
        } catch (err) {
            this._logging.warn('Skipping population of members_monthly_price_id, could not parse stripe_plans');
        }

        const monthlyPlan = plans.find((plan) => {
            return plan.name === 'Monthly';
        });

        if (!monthlyPlan) {
            this._logging.warn('Skipping population of members_monthly_price_id, could not find Monthly plan');
            return;
        }

        let monthlyPrice;

        monthlyPrice = await this._StripePrice.findOne({
            amount: monthlyPlan.amount,
            currency: monthlyPlan.currency,
            interval: monthlyPlan.interval,
            active: true
        });

        if (!monthlyPrice) {
            this._logging.info('Could not find active Monthly price from stripe_plans - searching by interval');
            monthlyPrice = await this._StripePrice.findOne({
                interval: 'month',
                active: true
            });
        }

        if (!monthlyPrice) {
            this._logging.info('Could not any active Monthly price - creating a new one');
            let defaultStripeProduct;
            const stripeProductsPage = await this._StripeProduct.findPage({limit: 1});
            defaultStripeProduct = stripeProductsPage.data[0];
            const price = await this._stripeAPIService.createPrice({
                currency: 'usd',
                amount: 5000,
                nickname: 'Monthly',
                interval: 'month',
                active: true,
                type: 'recurring',
                product: defaultStripeProduct.get('stripe_product_id')
            });

            monthlyPrice = await this._StripePrice.add({
                stripe_price_id: price.id,
                stripe_product_id: defaultStripeProduct.get('stripe_product_id'),
                active: price.active,
                nickname: price.nickname,
                currency: price.currency,
                amount: price.unit_amount,
                type: 'recurring',
                interval: price.recurring.interval
            });
        }

        await this._Settings.edit({key: 'members_monthly_price_id', value: monthlyPrice.id}, {id: monthlyPriceId.id});
    }

    async populateMembersYearlyPriceIdSettings() {
        this._logging.info('Populating members_yearly_price_id from stripe_plans');
        const yearlyPriceId = await this._Settings.findOne({key: 'members_yearly_price_id'});

        if (yearlyPriceId.get('value')) {
            this._logging.info('Skipping population of members_yearly_price_id, already populated');
            return;
        }

        const stripePlans = await this._Settings.findOne({key: 'stripe_plans'});
        let plans;
        try {
            plans = JSON.parse(stripePlans.get('value'));
        } catch (err) {
            this._logging.warn('Skipping population of members_yearly_price_id, could not parse stripe_plans');
        }

        const yearlyPlan = plans.find((plan) => {
            return plan.name === 'Yearly';
        });

        if (!yearlyPlan) {
            this._logging.warn('Skipping population of members_yearly_price_id, could not find yearly plan');
            return;
        }

        let yearlyPrice;

        yearlyPrice = await this._StripePrice.findOne({
            amount: yearlyPlan.amount,
            currency: yearlyPlan.currency,
            interval: yearlyPlan.interval,
            active: true
        });

        if (!yearlyPrice) {
            this._logging.info('Could not find active yearly price from stripe_plans - searching by interval');
            yearlyPrice = await this._StripePrice.findOne({
                interval: 'year',
                active: true
            });
        }

        if (!yearlyPrice) {
            this._logging.info('Could not any active yearly price - creating a new one');
            let defaultStripeProduct;
            const stripeProductsPage = await this._StripeProduct.findPage({limit: 1});
            defaultStripeProduct = stripeProductsPage.data[0];
            const price = await this._stripeAPIService.createPrice({
                currency: 'usd',
                amount: 500,
                nickname: 'Yearly',
                interval: 'year',
                active: true,
                type: 'recurring',
                product: defaultStripeProduct.get('stripe_product_id')
            });

            yearlyPrice = await this._StripePrice.add({
                stripe_price_id: price.id,
                stripe_product_id: defaultStripeProduct.get('stripe_product_id'),
                active: price.active,
                nickname: price.nickname,
                currency: price.currency,
                amount: price.unit_amount,
                type: 'recurring',
                interval: price.recurring.interval
            });
        }

        await this._Settings.edit({key: 'members_yearly_price_id', value: yearlyPrice.id}, {id: yearlyPriceId.id});
    }
};
<commit_msg>Updated migration to use getPrice method<commit_after>const _ = require('lodash');

/**
 * @typedef {object} ILogger
 * @prop {(x: any) => void} error
 * @prop {(x: any) => void} info
 * @prop {(x: any) => void} warn
 */
module.exports = class StripeMigrations {
    /**
     * StripeMigrations
     *
     * @param {object} params
     *
     * @param {ILogger} params.logger
     * @param {any} params.StripeCustomerSubscription
     * @param {any} params.StripeProduct
     * @param {any} params.StripePrice
     * @param {any} params.Product
     * @param {any} params.Settings
     * @param {import('../services/stripe-api')} params.stripeAPIService
     */
    constructor({
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        Settings,
        stripeAPIService,
        logger
    }) {
        this._logging = logger;
        this._StripeCustomerSubscription = StripeCustomerSubscription;
        this._StripeProduct = StripeProduct;
        this._StripePrice = StripePrice;
        this._Product = Product;
        this._Settings = Settings;
        this._stripeAPIService = stripeAPIService;
    }

    async populateProductsAndPrices() {
        const subscriptionModels = await this._StripeCustomerSubscription.findAll();
        const priceModels = await this._StripePrice.findAll();
        const productModels = await this._StripeProduct.findAll();
        const subscriptions = subscriptionModels.toJSON();
        const prices = priceModels.toJSON();
        const products = productModels.toJSON();
        const {data} = await this._Product.findPage({
            limit: 1
        });
        const defaultProduct = data[0] && data[0].toJSON();

        if (subscriptions.length > 0 && products.length === 0 && prices.length === 0 && defaultProduct) {
            try {
                this._logging.info(`Populating products and prices for existing stripe customers`);
                const uniquePlans = _.uniq(subscriptions.map(d => _.get(d, 'plan.id')));

                let stripePrices = [];
                for (const plan of uniquePlans) {
                    try {
                        const stripePrice = await this._stripeAPIService.getPrice(plan, {
                            expand: ['product']
                        });
                        stripePrices.push(stripePrice);
                    } catch (err) {
                        if (err && err.statusCode === 404) {
                            this._logging.warn(`Plan ${plan} not found on Stripe - ignoring`);
                        } else {
                            throw err;
                        }
                    }
                }
                this._logging.info(`Adding ${stripePrices.length} prices from Stripe`);
                for (const stripePrice of stripePrices) {
                    // We expanded the product when fetching this price.
                    /** @type {import('stripe').Stripe.Product} */
                    const stripeProduct = (stripePrice.product);

                    await this._StripeProduct.upsert({
                        product_id: defaultProduct.id,
                        stripe_product_id: stripeProduct.id
                    });

                    await this._StripePrice.add({
                        stripe_price_id: stripePrice.id,
                        stripe_product_id: stripeProduct.id,
                        active: stripePrice.active,
                        nickname: stripePrice.nickname,
                        currency: stripePrice.currency,
                        amount: stripePrice.unit_amount,
                        type: 'recurring',
                        interval: stripePrice.recurring.interval
                    });
                }
            } catch (e) {
                this._logging.error(`Failed to populate products/prices from stripe`);
                this._logging.error(e);
            }
        }
    }

    async findPriceByPlan(plan) {
        const currency = plan.currency ? plan.currency.toLowerCase() : 'usd';
        const amount = Number.isInteger(plan.amount) ? plan.amount : parseInt(plan.amount);
        const interval = plan.interval;

        const price = await this._StripePrice.findOne({
            currency,
            amount,
            interval
        });

        return price;
    }

    async populateStripePricesFromStripePlansSetting(plans) {
        if (!plans) {
            this._logging.info('Skipping stripe_plans -> stripe_prices migration');
            return;
        }
        let defaultStripeProduct;
        const stripeProductsPage = await this._StripeProduct.findPage({limit: 1});
        defaultStripeProduct = stripeProductsPage.data[0];

        if (!defaultStripeProduct) {
            this._logging.info('Could not find Stripe Product - creating one');
            const productsPage = await this._Product.findPage({limit: 1});
            const defaultProduct = productsPage.data[0];
            const stripeProduct = await this._stripeAPIService.createProduct({
                name: defaultProduct.get('name')
            });
            if (!defaultProduct) {
                this._logging.error('Could not find Product - skipping stripe_plans -> stripe_prices migration');
                return;
            }
            defaultStripeProduct = await this._StripeProduct.add({
                product_id: defaultProduct.id,
                stripe_product_id: stripeProduct.id
            });
        }

        for (const plan of plans) {
            const price = await this.findPriceByPlan(plan);

            if (!price) {
                this._logging.info(`Could not find Stripe Price ${JSON.stringify(plan)}`);

                try {
                    this._logging.info(`Creating Stripe Price ${JSON.stringify(plan)}`);
                    const price = await this._stripeAPIService.createPrice({
                        currency: plan.currency,
                        amount: plan.amount,
                        nickname: plan.name,
                        interval: plan.interval,
                        active: true,
                        type: 'recurring',
                        product: defaultStripeProduct.get('stripe_product_id')
                    });

                    await this._StripePrice.add({
                        stripe_price_id: price.id,
                        stripe_product_id: defaultStripeProduct.get('stripe_product_id'),
                        active: price.active,
                        nickname: price.nickname,
                        currency: price.currency,
                        amount: price.unit_amount,
                        type: 'recurring',
                        interval: price.recurring.interval
                    });
                } catch (err) {
                    this._logging.error({err, message: 'Adding price failed'});
                }
            }
        }
    }

    async updatePortalPlansSetting(plans) {
        this._logging.info('Migrating portal_plans setting from names to ids');
        const portalPlansSetting = await this._Settings.findOne({key: 'portal_plans'});

        let portalPlans;
        try {
            portalPlans = JSON.parse(portalPlansSetting.get('value'));
        } catch (err) {
            this._logging.error({
                message: 'Could not parse portal_plans setting, skipping migration',
                err
            });
            return;
        }

        const containsOldValues = !!portalPlans.find((plan) => {
            return ['monthly', 'yearly'].includes(plan);
        });

        if (!containsOldValues) {
            this._logging.info('Could not find names in portal_plans setting, skipping migration');
            return;
        }

        const newPortalPlans = await portalPlans.reduce(async (newPortalPlansPromise, plan) => {
            let newPlan = plan;
            if (plan === 'monthly') {
                const monthlyPlan = plans.find((plan) => {
                    return plan.name === 'Monthly';
                });
                if (!monthlyPlan) {
                    return newPortalPlansPromise;
                }
                const price = await this.findPriceByPlan(monthlyPlan);
                newPlan = price.id;
            }
            if (plan === 'yearly') {
                const yearlyPlan = plans.find((plan) => {
                    return plan.name === 'Yearly';
                });
                if (!yearlyPlan) {
                    return newPortalPlansPromise;
                }
                const price = await this.findPriceByPlan(yearlyPlan);
                newPlan = price.id;
            }
            const newPortalPlans = await newPortalPlansPromise;
            return newPortalPlans.concat(newPlan);
        }, []);

        this._logging.info(`Updating portal_plans setting to ${JSON.stringify(newPortalPlans)}`);
        await this._Settings.edit({
            key: 'portal_plans',
            value: JSON.stringify(newPortalPlans)
        }, {
            id: portalPlansSetting.id
        });
    }

    async populateMembersMonthlyPriceIdSettings() {
        this._logging.info('Populating members_monthly_price_id from stripe_plans');
        const monthlyPriceId = await this._Settings.findOne({key: 'members_monthly_price_id'});

        if (monthlyPriceId.get('value')) {
            this._logging.info('Skipping population of members_monthly_price_id, already populated');
            return;
        }

        const stripePlans = await this._Settings.findOne({key: 'stripe_plans'});
        let plans;
        try {
            plans = JSON.parse(stripePlans.get('value'));
        } catch (err) {
            this._logging.warn('Skipping population of members_monthly_price_id, could not parse stripe_plans');
        }

        const monthlyPlan = plans.find((plan) => {
            return plan.name === 'Monthly';
        });

        if (!monthlyPlan) {
            this._logging.warn('Skipping population of members_monthly_price_id, could not find Monthly plan');
            return;
        }

        let monthlyPrice;

        monthlyPrice = await this._StripePrice.findOne({
            amount: monthlyPlan.amount,
            currency: monthlyPlan.currency,
            interval: monthlyPlan.interval,
            active: true
        });

        if (!monthlyPrice) {
            this._logging.info('Could not find active Monthly price from stripe_plans - searching by interval');
            monthlyPrice = await this._StripePrice.findOne({
                interval: 'month',
                active: true
            });
        }

        if (!monthlyPrice) {
            this._logging.info('Could not any active Monthly price - creating a new one');
            let defaultStripeProduct;
            const stripeProductsPage = await this._StripeProduct.findPage({limit: 1});
            defaultStripeProduct = stripeProductsPage.data[0];
            const price = await this._stripeAPIService.createPrice({
                currency: 'usd',
                amount: 5000,
                nickname: 'Monthly',
                interval: 'month',
                active: true,
                type: 'recurring',
                product: defaultStripeProduct.get('stripe_product_id')
            });

            monthlyPrice = await this._StripePrice.add({
                stripe_price_id: price.id,
                stripe_product_id: defaultStripeProduct.get('stripe_product_id'),
                active: price.active,
                nickname: price.nickname,
                currency: price.currency,
                amount: price.unit_amount,
                type: 'recurring',
                interval: price.recurring.interval
            });
        }

        await this._Settings.edit({key: 'members_monthly_price_id', value: monthlyPrice.id}, {id: monthlyPriceId.id});
    }

    async populateMembersYearlyPriceIdSettings() {
        this._logging.info('Populating members_yearly_price_id from stripe_plans');
        const yearlyPriceId = await this._Settings.findOne({key: 'members_yearly_price_id'});

        if (yearlyPriceId.get('value')) {
            this._logging.info('Skipping population of members_yearly_price_id, already populated');
            return;
        }

        const stripePlans = await this._Settings.findOne({key: 'stripe_plans'});
        let plans;
        try {
            plans = JSON.parse(stripePlans.get('value'));
        } catch (err) {
            this._logging.warn('Skipping population of members_yearly_price_id, could not parse stripe_plans');
        }

        const yearlyPlan = plans.find((plan) => {
            return plan.name === 'Yearly';
        });

        if (!yearlyPlan) {
            this._logging.warn('Skipping population of members_yearly_price_id, could not find yearly plan');
            return;
        }

        let yearlyPrice;

        yearlyPrice = await this._StripePrice.findOne({
            amount: yearlyPlan.amount,
            currency: yearlyPlan.currency,
            interval: yearlyPlan.interval,
            active: true
        });

        if (!yearlyPrice) {
            this._logging.info('Could not find active yearly price from stripe_plans - searching by interval');
            yearlyPrice = await this._StripePrice.findOne({
                interval: 'year',
                active: true
            });
        }

        if (!yearlyPrice) {
            this._logging.info('Could not any active yearly price - creating a new one');
            let defaultStripeProduct;
            const stripeProductsPage = await this._StripeProduct.findPage({limit: 1});
            defaultStripeProduct = stripeProductsPage.data[0];
            const price = await this._stripeAPIService.createPrice({
                currency: 'usd',
                amount: 500,
                nickname: 'Yearly',
                interval: 'year',
                active: true,
                type: 'recurring',
                product: defaultStripeProduct.get('stripe_product_id')
            });

            yearlyPrice = await this._StripePrice.add({
                stripe_price_id: price.id,
                stripe_product_id: defaultStripeProduct.get('stripe_product_id'),
                active: price.active,
                nickname: price.nickname,
                currency: price.currency,
                amount: price.unit_amount,
                type: 'recurring',
                interval: price.recurring.interval
            });
        }

        await this._Settings.edit({key: 'members_yearly_price_id', value: yearlyPrice.id}, {id: yearlyPriceId.id});
    }
};
<|endoftext|>


<commit_before>/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.*
 */

const assert = require('assert');
const Events = require('../Events');
const Trigger = require('./Trigger');

const DEBUG = false;

const ops = {
  AND: 'AND',
  OR: 'OR',
};

/**
 * A Trigger which activates only when a set of triggers are activated
 */
class MultiTrigger extends Trigger {
  /**
   * @param {TriggerDescription} desc
   */
  constructor(desc) {
    super(desc);
    assert(desc.op in ops);
    this.op = desc.op;
    const fromDescription = require('./index').fromDescription;

    if (DEBUG) {
      this.id = Math.floor(Math.random() * 1000);
    }
    this.triggers = desc.triggers.map((trigger) => {
      return fromDescription(trigger);
    });

    this.states = new Array(this.triggers.length);
    this.state = false;
  }

  /**
   * @return {TriggerDescription}
   */
  toDescription() {
    return Object.assign(super.toDescription(), {
      op: this.op,
      triggers: this.triggers.map((trigger) => trigger.toDescription()),
    });
  }

  async start() {
    const starts = this.triggers.map((trigger, triggerIndex) => {
      trigger.on(Events.STATE_CHANGED,
                 this.onStateChanged.bind(this, triggerIndex));
      return trigger.start();
    });
    await Promise.all(starts);
  }

  stop() {
    this.triggers.forEach((trigger) => {
      trigger.removeAllListeners(Events.STATE_CHANGED);
      trigger.stop();
    });
  }

  onStateChanged(triggerIndex, state) {
    this.states[triggerIndex] = state.on;

    let value = this.states[0];
    for (let i = 1; i < this.states.length; i++) {
      if (this.op === ops.AND) {
        value = value && this.states[i];
      } else if (this.op === ops.OR) {
        value = value || this.states[i];
      }
    }
    if (DEBUG) {
      console.debug(
        `MultiTrigger(${this.id}).onStateChanged(${triggerIndex}, ${state}) -> ${this.states}`);
    }
    if (value !== this.state) {
      this.state = value;
      this.emit(Events.STATE_CHANGED, {on: this.state});
    }
  }
}

module.exports = MultiTrigger;
<commit_msg>Properly initialize MultiTrigger states<commit_after>/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.*
 */

const assert = require('assert');
const Events = require('../Events');
const Trigger = require('./Trigger');

const DEBUG = false;

const ops = {
  AND: 'AND',
  OR: 'OR',
};

/**
 * A Trigger which activates only when a set of triggers are activated
 */
class MultiTrigger extends Trigger {
  /**
   * @param {TriggerDescription} desc
   */
  constructor(desc) {
    super(desc);
    assert(desc.op in ops);
    this.op = desc.op;
    const fromDescription = require('./index').fromDescription;

    if (DEBUG) {
      this.id = Math.floor(Math.random() * 1000);
    }
    this.triggers = desc.triggers.map((trigger) => {
      return fromDescription(trigger);
    });

    this.states = new Array(this.triggers.length);
    for (let i = 0; i < this.states.length; i++) {
      this.states[i] = false;
    }
    this.state = false;
  }

  /**
   * @return {TriggerDescription}
   */
  toDescription() {
    return Object.assign(super.toDescription(), {
      op: this.op,
      triggers: this.triggers.map((trigger) => trigger.toDescription()),
    });
  }

  async start() {
    const starts = this.triggers.map((trigger, triggerIndex) => {
      trigger.on(Events.STATE_CHANGED,
                 this.onStateChanged.bind(this, triggerIndex));
      return trigger.start();
    });
    await Promise.all(starts);
  }

  stop() {
    this.triggers.forEach((trigger) => {
      trigger.removeAllListeners(Events.STATE_CHANGED);
      trigger.stop();
    });
  }

  onStateChanged(triggerIndex, state) {
    this.states[triggerIndex] = state.on;

    let value = this.states[0];
    for (let i = 1; i < this.states.length; i++) {
      if (this.op === ops.AND) {
        value = value && this.states[i];
      } else if (this.op === ops.OR) {
        value = value || this.states[i];
      }
    }
    if (DEBUG) {
      console.debug(
        `MultiTrigger(${this.id}).onStateChanged(${triggerIndex}, ${state}) -> ${this.states}`);
    }
    if (value !== this.state) {
      this.state = value;
      this.emit(Events.STATE_CHANGED, {on: this.state});
    }
  }
}

module.exports = MultiTrigger;
<|endoftext|>


<commit_before>import React from 'react';

import Checkbox from './checkbox';
import Colour from './colour';
import Dropdown from './dropdown';
import Number from './number';
import Radio from './radio';
import RadioGroup from './radiogroup';
import Range from './range';
import Textbox from './textbox';

import Fonts from '../../constants/fonts';

import Chrome from '../../modules/chrome';

class Options extends React.Component {
    constructor (props) {
        super(props);

        this.state = {
            settings : {}
        };
    }

    componentDidMount () {
        Chrome.getSettings((settings) => {
            this.setState({
                settings : settings
            });
        });
    }

    render () {
        let settings = this.state.settings;

        return (

General

                <Checkbox label='24-hour format'
                    tooltip='Toggle between 12-hour and 24-hour formats.'
                    optkey='time24hr'
                    value={settings.time24hr} />

                <Checkbox label='Animations'
                    tooltip='Enables animations.'
                    optkey='animations'
                    value={settings.animations} />

                <Checkbox label='Show the time'
                    tooltip='Shows the current time.'
                    optkey='showTime'
                    value={settings.showTime} />

                <Checkbox label='Show colour hexcode'
                    tooltip='Shows the current colour hexcode underneath the time.'
                    optkey='showHex'
                    value={settings.showHex} />

Shortcuts

                <Checkbox label='Show settings shortcut'
                    tooltip='Shortcut to open Colour New Tab options.'
                    optkey='shortcutOpts'
                    value={settings.shortcutOpts} />

                <Checkbox label='Show default new tab shortcut'
                    tooltip='Shortcut to open the default Chrome new tab page.'
                    optkey='shortcutNewTab'
                    value={settings.shortcutNewTab} />

                <Checkbox label='Show image download shortcut'
                    tooltip='Shortcut to open the background image, if present.'
                    optkey='shortcutImage'
                    value={settings.shortcutImage} />

Colours

                <RadioGroup group='colourtype' optkey='colour' value={settings.colour}>
                    <Radio label='Regular'
                        tooltip='Shows the corresponding colour based on the 24-hour clock.'
                        value='regular' />

                    <Radio label='Full spectrum hexadecimal'
                        tooltip='A new colour for every second, going from #000000 to #FFFFFF in one day.'
                        value='full' />

                    <Radio label='Full spectrum hue'
                        tooltip='A slow shift across the entire hue spectrum, from #FF0000 to #00FFFF and back in one day.'
                        value='hue' />

                    <Radio label='Solid colour'
                        tooltip='A solid, non-changing colour.'
                        value='solid'>
                        <Colour label='Solid colour'
                            optkey='colourSolid'
                            value={settings.colourSolid} />
                    </Radio>
                </RadioGroup>

                <Checkbox label='Show ticker of past colours'
                    tooltip='Shows last 10 colours in a ticker at the bottom of the page.'
                    optkey='ticker'
                    value={settings.ticker} />

Background image

                <RadioGroup group='bg' optkey='bg' value={settings.bg}>
                    <Radio label='None'
                        tooltip='No background image.'
                        value='none' />

                    <Radio label='Unsplash'
                        tooltip='A random image from unsplash.com.'
                        value='unsplash'>
                        <RadioGroup group='bgUnsplashFreq'
                            optkey='bgUnsplashFreq' value={settings.bgUnsplashFreq}>
                            <Radio label='Per session'
                                tooltip='A new image every time.'
                                value='perSession' />

                            <Radio label='Daily'
                                tooltip='A new image every day.'
                                value='daily' />

                            <Radio label='Weekly'
                                tooltip='A new image every week.'
                                value='weekly' />
                        </RadioGroup>
                    </Radio>

                    <Radio label='Custom'
                        tooltip='A custom image.'
                        value='custom'>
                        <Textbox label='Image URL'
                            optkey='bgCustomUrl'
                            value={settings.bgCustomUrl} />
                    </Radio>
                </RadioGroup>

                <Range label='Colour overlay opacity'
                    optkey='bgOpacity'
                    value={settings.bgOpacity} />

Font

                <RadioGroup group='font' optkey='font' value={settings.font}>
                    <Radio label='Default'
                        tooltip='Default Open Sans font.'
                        value='default' />

                    <Radio label='Web font'
                        tooltip='Custom font from Google Fonts.'
                        value='web'>
                        <Dropdown label='Font'
                            tooltip='Custom font from Google Fonts.'
                            options={Fonts}
                            optkey='fontWeb'
                            value={settings.fontWeb} />
                    </Radio>
                </RadioGroup>

Panels

                <Checkbox label='Most visited'
                    tooltip='Your most visited pages.'
                    optkey='panelVisited'
                    value={settings.panelVisited}>
                    <Number label='Max number of most visited pages'
                        optkey='maxVisited'
                        value={settings.maxVisited} />
                </Checkbox>

                <Checkbox label='Recently closed'
                    tooltip='Recently closed tabs and windows.'
                    optkey='panelClosed'
                    value={settings.panelClosed}>
                    <Number label='Max number of recently closed pages'
                        optkey='maxClosed'
                        value={settings.maxClosed} />
                </Checkbox>

                <Checkbox label='Other devices'
                    tooltip='Show tabs from your other devices.'
                    optkey='panelDevices'
                    value={settings.panelDevices} />

                <Checkbox label='Apps'
                    tooltip='Your installed Chrome apps.'
                    optkey='panelApps'
                    value={settings.panelApps}>
                    <Checkbox label='Show "All apps" tile'
                        tooltip="Show a tile for Chrome's default apps page."
                        optkey='showAllApps'
                        value={settings.showAllApps} />

                    <Checkbox label='Show "Web store" tile'
                        tooltip='Show a tile for the Chrome Web Store.'
                        optkey='showWebStore'
                        value={settings.showWebStore} />
                </Checkbox>

                <Checkbox label='Shortcuts'
                    tooltip='Various Chrome shortcuts.'
                    optkey='panelShortcuts'
                    value={settings.panelShortcuts} />

        );
    }
};

export default Options;
<commit_msg>Minor wording edits<commit_after>import React from 'react';

import Checkbox from './checkbox';
import Colour from './colour';
import Dropdown from './dropdown';
import Number from './number';
import Radio from './radio';
import RadioGroup from './radiogroup';
import Range from './range';
import Textbox from './textbox';

import Fonts from '../../constants/fonts';

import Chrome from '../../modules/chrome';

class Options extends React.Component {
    constructor (props) {
        super(props);

        this.state = {
            settings : {}
        };
    }

    componentDidMount () {
        Chrome.getSettings((settings) => {
            this.setState({
                settings : settings
            });
        });
    }

    render () {
        let settings = this.state.settings;

        return (

General

                <Checkbox label='24-hour format'
                    tooltip='Toggle between 12-hour and 24-hour formats.'
                    optkey='time24hr'
                    value={settings.time24hr} />

                <Checkbox label='Animations'
                    tooltip='Enables animations.'
                    optkey='animations'
                    value={settings.animations} />

                <Checkbox label='Show the time'
                    tooltip='Shows the current time.'
                    optkey='showTime'
                    value={settings.showTime} />

                <Checkbox label='Show colour hexcode'
                    tooltip='Shows the current colour hexcode underneath the time.'
                    optkey='showHex'
                    value={settings.showHex} />

Shortcuts

                <Checkbox label='Settings'
                    tooltip='Shortcut to open Colour New Tab options.'
                    optkey='shortcutOpts'
                    value={settings.shortcutOpts} />

                <Checkbox label='Default new tab'
                    tooltip='Shortcut to open the default Chrome new tab page.'
                    optkey='shortcutNewTab'
                    value={settings.shortcutNewTab} />

                <Checkbox label='Image download'
                    tooltip='Shortcut to open the background image, if present.'
                    optkey='shortcutImage'
                    value={settings.shortcutImage} />

Colours

                <RadioGroup group='colourtype' optkey='colour' value={settings.colour}>
                    <Radio label='Regular'
                        tooltip='Shows the corresponding colour based on the 24-hour clock.'
                        value='regular' />

                    <Radio label='Full spectrum hexadecimal'
                        tooltip='A new colour for every second, going from #000000 to #FFFFFF in one day.'
                        value='full' />

                    <Radio label='Full spectrum hue'
                        tooltip='A slow shift across the entire hue spectrum, from #FF0000 to #00FFFF and back in one day.'
                        value='hue' />

                    <Radio label='Solid colour'
                        tooltip='A solid, non-changing colour.'
                        value='solid'>
                        <Colour label='Solid colour'
                            optkey='colourSolid'
                            value={settings.colourSolid} />
                    </Radio>
                </RadioGroup>

                <Checkbox label='Show ticker of past colours'
                    tooltip='Shows last 10 colours in a ticker at the bottom of the page.'
                    optkey='ticker'
                    value={settings.ticker} />

Background image

                <RadioGroup group='bg' optkey='bg' value={settings.bg}>
                    <Radio label='None'
                        tooltip='No background image.'
                        value='none' />

                    <Radio label='Unsplash'
                        tooltip='A random image from unsplash.com.'
                        value='unsplash'>
                        <RadioGroup group='bgUnsplashFreq'
                            optkey='bgUnsplashFreq' value={settings.bgUnsplashFreq}>
                            <Radio label='Per session'
                                tooltip='A new image every time.'
                                value='perSession' />

                            <Radio label='Daily'
                                tooltip='A new image every day.'
                                value='daily' />

                            <Radio label='Weekly'
                                tooltip='A new image every week.'
                                value='weekly' />
                        </RadioGroup>
                    </Radio>

                    <Radio label='Custom'
                        tooltip='A custom image.'
                        value='custom'>
                        <Textbox label='Image URL'
                            optkey='bgCustomUrl'
                            value={settings.bgCustomUrl} />
                    </Radio>
                </RadioGroup>

                <Range label='Colour overlay opacity'
                    optkey='bgOpacity'
                    value={settings.bgOpacity} />

Font

                <RadioGroup group='font' optkey='font' value={settings.font}>
                    <Radio label='Default'
                        tooltip='Default Open Sans font.'
                        value='default' />

                    <Radio label='Web font'
                        tooltip='Custom font from Google Fonts.'
                        value='web'>
                        <Dropdown label='Font'
                            tooltip='Custom font from Google Fonts.'
                            options={Fonts}
                            optkey='fontWeb'
                            value={settings.fontWeb} />
                    </Radio>
                </RadioGroup>

Panels

                <Checkbox label='Most visited'
                    tooltip='Your most visited pages.'
                    optkey='panelVisited'
                    value={settings.panelVisited}>
                    <Number label='Max number of most visited pages'
                        optkey='maxVisited'
                        value={settings.maxVisited} />
                </Checkbox>

                <Checkbox label='Recently closed'
                    tooltip='Recently closed tabs and windows.'
                    optkey='panelClosed'
                    value={settings.panelClosed}>
                    <Number label='Max number of recently closed pages'
                        optkey='maxClosed'
                        value={settings.maxClosed} />
                </Checkbox>

                <Checkbox label='Other devices'
                    tooltip='Show tabs from your other devices.'
                    optkey='panelDevices'
                    value={settings.panelDevices} />

                <Checkbox label='Apps'
                    tooltip='Your installed Chrome apps.'
                    optkey='panelApps'
                    value={settings.panelApps}>
                    <Checkbox label='Show "All apps" tile'
                        tooltip="Show a tile for Chrome's default apps page."
                        optkey='showAllApps'
                        value={settings.showAllApps} />

                    <Checkbox label='Show "Web store" tile'
                        tooltip='Show a tile for the Chrome Web Store.'
                        optkey='showWebStore'
                        value={settings.showWebStore} />
                </Checkbox>

                <Checkbox label='Shortcuts'
                    tooltip='Various Chrome shortcuts.'
                    optkey='panelShortcuts'
                    value={settings.panelShortcuts} />

        );
    }
};

export default Options;
<|endoftext|>


<commit_before>var frontend    = require('../controllers/frontend'),
    config      = require('../config'),
    express     = require('express'),
    utils       = require('../utils'),

    frontendRoutes;

frontendRoutes = function (middleware) {
    var router = express.Router(),
        subdir = config.paths.subdir,
        routeKeywords = config.routeKeywords;

    // ### Admin routes
    router.get(/^\/(logout|signout)\/$/, function redirect(req, res) {
        /*jslint unparam:true*/
        res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
        res.redirect(301, subdir + '/ghost/signout/');
    });
    router.get(/^\/signup\/$/, function redirect(req, res) {
        /*jslint unparam:true*/
        res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
        res.redirect(301, subdir + '/ghost/signup/');
    });

    // redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc.
    router.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin|login)\/?)$/, function (req, res) {
        /*jslint unparam:true*/
        res.redirect(subdir + '/ghost/');
    });

    // password-protected frontend route
    router.get('/' + routeKeywords.private + '/',
        middleware.isPrivateSessionAuth,
        frontend.private
    );
    router.post('/' + routeKeywords.private + '/',
        middleware.isPrivateSessionAuth,
        middleware.spamProtectedPrevention,
        middleware.authenticateProtection,
        frontend.private
    );

    // ### Frontend routes
    router.get('/rss/', frontend.rss);
    router.get('/rss/:page/', frontend.rss);
    router.get('/feed/', function redirect(req, res) {
        /*jshint unused:true*/
        res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
        res.redirect(301, subdir + '/rss/');
    });

    // Tags
    router.get('/' + routeKeywords.tag + '/:slug/rss/', frontend.rss);
    router.get('/' + routeKeywords.tag + '/:slug/rss/:page/', frontend.rss);
    router.get('/' + routeKeywords.tag + '/:slug/' + routeKeywords.page + '/:page/', frontend.tag);
    router.get('/' + routeKeywords.tag + '/:slug/', frontend.tag);

    // Authors
    router.get('/' + routeKeywords.author + '/:slug/rss/', frontend.rss);
    router.get('/' + routeKeywords.author + '/:slug/rss/:page/', frontend.rss);
    router.get('/' + routeKeywords.author + '/:slug/' + routeKeywords.page + '/:page/', frontend.author);
    router.get('/' + routeKeywords.author + '/:slug/', frontend.author);

    // Post Live Preview
    router.get('/' + routeKeywords.preview + '/:uuid', frontend.preview);

    // Default
    router.get('/' + routeKeywords.page + '/:page/', frontend.homepage);
    router.get('/', frontend.homepage);
    router.get('*', frontend.single);

    // custom code for adding a contact form with nodemailer
    // if you want to pass arguments to the function
    // it must be router.get instead of tarikk’s router.post

    // router.get('/mail/', middleware.authenticateClient, frontend.submitContactForm);
    // this is an experiment

    router.get('/mail/', function (res, req) {
        res.header("Access-Control-Allow-Origin", "*");
        frontend.submitContactForm;
    });
    // end of custom code

    return router;
};

module.exports = frontendRoutes;
<commit_msg>try cross domain access<commit_after>var frontend    = require('../controllers/frontend'),
    config      = require('../config'),
    express     = require('express'),
    utils       = require('../utils'),

    frontendRoutes;

frontendRoutes = function (middleware) {
    var router = express.Router(),
        subdir = config.paths.subdir,
        routeKeywords = config.routeKeywords;

    // ### Admin routes
    router.get(/^\/(logout|signout)\/$/, function redirect(req, res) {
        /*jslint unparam:true*/
        res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
        res.redirect(301, subdir + '/ghost/signout/');
    });
    router.get(/^\/signup\/$/, function redirect(req, res) {
        /*jslint unparam:true*/
        res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
        res.redirect(301, subdir + '/ghost/signup/');
    });

    // redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc.
    router.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin|login)\/?)$/, function (req, res) {
        /*jslint unparam:true*/
        res.redirect(subdir + '/ghost/');
    });

    // password-protected frontend route
    router.get('/' + routeKeywords.private + '/',
        middleware.isPrivateSessionAuth,
        frontend.private
    );
    router.post('/' + routeKeywords.private + '/',
        middleware.isPrivateSessionAuth,
        middleware.spamProtectedPrevention,
        middleware.authenticateProtection,
        frontend.private
    );

    // ### Frontend routes
    router.get('/rss/', frontend.rss);
    router.get('/rss/:page/', frontend.rss);
    router.get('/feed/', function redirect(req, res) {
        /*jshint unused:true*/
        res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
        res.redirect(301, subdir + '/rss/');
    });

    // Tags
    router.get('/' + routeKeywords.tag + '/:slug/rss/', frontend.rss);
    router.get('/' + routeKeywords.tag + '/:slug/rss/:page/', frontend.rss);
    router.get('/' + routeKeywords.tag + '/:slug/' + routeKeywords.page + '/:page/', frontend.tag);
    router.get('/' + routeKeywords.tag + '/:slug/', frontend.tag);

    // Authors
    router.get('/' + routeKeywords.author + '/:slug/rss/', frontend.rss);
    router.get('/' + routeKeywords.author + '/:slug/rss/:page/', frontend.rss);
    router.get('/' + routeKeywords.author + '/:slug/' + routeKeywords.page + '/:page/', frontend.author);
    router.get('/' + routeKeywords.author + '/:slug/', frontend.author);

    // Post Live Preview
    router.get('/' + routeKeywords.preview + '/:uuid', frontend.preview);

    // Default
    router.get('/' + routeKeywords.page + '/:page/', frontend.homepage);
    router.get('/', frontend.homepage);
    router.get('*', frontend.single);

    // custom code for adding a contact form with nodemailer
    // if you want to pass arguments to the function
    // it must be router.get instead of tarikk’s router.post

    // router.get('/mail/', middleware.authenticateClient, frontend.submitContactForm);

    // router.get('/mail/', function (res, req) {
    //     res.header("Access-Control-Allow-Origin", "*");
    //     frontend.submitContactForm;
    // });

    // this is an experiment

    router.get('/mail/', frontend.submitContactForm);
    // end of custom code

    return router;
};

module.exports = frontendRoutes;
<|endoftext|>


<commit_before>// Copyright (C) 2014 Makoto Mizukami

(function () {
	"use strict";

	var DEFAULTS = {
		timeout : 180 * 1000	// Unit: second
	},

	CONST = {
		FCGIVersion : 1,
		FCGINullReqId : 0,
		FCGIRecordTypes : [
			undefined,
			"FCGI_BEGIN_REQUEST",
			"FCGI_ABORT_REQUEST",
			"FCGI_END_REQUEST",
			"FCGI_PARAMS",
			"FCGI_STDIN",
			"FCGI_STDOUT",
			"FCGI_STDERR",
			"FCGI_DATA",
			"FCGI_GET_VALUES",
			"FCGI_GET_VALUES_RESULT",
			"FCGI_UNKNOWN_TYPE"
		],
		FCGIEndReasons : [
			"FCGI_REQUEST_COMPLETE",
			"FCGI_CANT_MPX_CONN",
			"FCGI_OVERLOADED",
			"FCGI_UNKNOWN_ROLE"
		],
		FCGIRole : 1,
		FCGIHeaderLength : 8,
		FCGIVersionOffset : 0,
		FCGITypeOffset : 1,
		FCGIReqIdOffset : 2,
		FCGIContentLengthOffset : 4,
		FCGIPaddingLengthOffset : 6,
		FCGIRoleOffset : 0,
		FCGIFlagsOffset : 2,
	},

	// Classes
	Server, ClientRequest, ServerResponse,
	// Methods
	createServer,
	// Internal functions
	defaultStatusPhrase, sendFCGIRecord, sendFCGIEndRecord, onConnection, onClientData, onClientError, onClientFin, onSocketError, onSocketClose, onSocketTimeout,
	// Internal values
	CRLF = "\r\n", wellknownStatuses = require("./http_status.js").IANAStatuses,

	EE = require("events").EventEmitter, net = require("net"), util = require("util");

	Server = function (requestListener) {
		net.Server.call(this, { allowHalfOpen : true });

		if (typeof requestListener === typeof Function) {
			this.on("request", requestListener);
		}

		this.on("connection", onConnection);
		this.on("clientError", onClientError);
	};
	util.inherits(Server, net.Server);

	Server.prototype.timeout = DEFAULTS.timeout;

	Server.prototype.setTimeout = function (timeoutAfter, callback) {
		this.timeout = timeoutAfter;

		if (callback !== undefined) {
			this.on("timeout", callback);
		}
	};

	ClientRequest = function (id, socket) {
		EE.call(this);

		this.reqId = id;
		this.socket = socket;
		this.params = {};

		this.on("close", this.removeAllListeners);
	};
	util.inherits(ClientRequest, EE);

	ClientRequest.prototype.setTimeout = function (timeoutAfter, callback) {
		this.socket.setTimeout(timeoutAfter);

		if (callback !== undefined) {
			this.socket.on("timeout", callback);
		}
	};

	ServerResponse = function (id, socket) {
		var headerInfo = {
			fields : {},
			fieldQueue : [],
		};

		EE.call(this);

		this.reqId = id;
		this.socket = socket;
		this.closeSocketOnEnd = false;

		this.setHeader = ServerResponse.prototype.setHeader.bind(this, headerInfo);
		this.flushHeaders = ServerResponse.prototype.flushHeaders.bind(this, headerInfo);

		this.setStatus(200);

		this.on("close", this.removeAllListeners);
		this.on("finish", this.removeAllListeners);
	};
	util.inherits(ServerResponse, EE);

	ServerResponse.prototype.headerFinalized = false;

	ServerResponse.prototype.setStatus = function (sCode, reasonPhrase) {
		this.setHeader(["Status:", sCode.toString(), reasonPhrase !== undefined ? reasonPhrase : defaultStatusPhrase(sCode)].join(" "));
	};

	ServerResponse.prototype.setHeader = function (headerInfo, headerStatement) {
		var fieldName = headerStatement.split(":").shift().toLowerCase(),
		queued = headerInfo.fieldQueue.indexOf(fieldName);

		if (headerInfo.fields[fieldName] !== undefined && queued === -1) {
			return;
		}

		headerInfo.fields[fieldName] = headerStatement.trim();

		if (queued >= 0) {
			headerInfo.fieldQueue.splice(queued, 1);
		}

		headerInfo.fieldQueue.push(fieldName);
	};

	ServerResponse.prototype.flushHeaders = function (headerInfo, finalizeHeader) {
		if (this.socket.writable !== true || this.headerFinalized === true) {
			return;
		}

		if (headerInfo.fieldQueue.length > 0) {
			headerInfo.fieldQueue.forEach(function (field) {
				sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(headerInfo.fields[field] + CRLF));
			}.bind(this));
		}
		headerInfo.fieldQueue = [];

		if (finalizeHeader === true) {
			sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(CRLF));
			this.headerFinalized = true;
		}
	};

	ServerResponse.prototype.write = function (data) {
		this.headerFinalized !== true && this.flushHeaders(true);
		sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(data));
	};

	ServerResponse.prototype.end = function (data) {
		data !== undefined && this.write(data);
		sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(0));
		sendFCGIEndRecord(this.socket, this.reqId, "FCGI_REQUEST_COMPLETE");

		if (this.closeSocketOnEnd) {
			if (this.socket._writableState.writing === false) {
				this.socket.end();
			} else {
				this.socket.once("drain", this.socket.end);
			}
		}
	};

	createServer = function (requestListener) {
		return new Server(requestListener);
	};

	defaultStatusPhrase = function (sCode) {
		return wellknownStatuses[sCode] !== undefined ? wellknownStatuses[sCode] : [sCode.toString(), "Unknown Reason"].join(" ");
	};

	sendFCGIRecord = function (socket, type, reqId, content) {
		var msgHeader = new Buffer(CONST.FCGIHeaderLength);

		msgHeader.fill(0);
		msgHeader[CONST.FCGIVersionOffset] = CONST.FCGIVersion;
		msgHeader[CONST.FCGITypeOffset] = CONST.FCGIRecordTypes.indexOf(type);
		msgHeader.writeUInt16BE(reqId, CONST.FCGIReqIdOffset);
		msgHeader.writeUInt16BE(content.length, CONST.FCGIContentLengthOffset);
		msgHeader[CONST.FCGIPaddingLengthOffset] = 0;

		if (socket.writable === true) {
			socket.write(Buffer.concat([msgHeader, content], CONST.FCGIHeaderLength + content.length));
		}
	};

	sendFCGIEndRecord = function (socket, reqId, reason) {
		sendFCGIRecord(socket, "FCGI_END_REQUEST", reqId, new Buffer([0, 0, 0, 0, CONST.FCGIEndReasons.indexOf(reason), 0, 0, 0]));
	};

	onConnection = function (socket) {
		var connection = {
			server : this,
			socket : socket,
			clientBuffered : null,
			sessions : []
		};

		socket.setTimeout(this.timeout);

		socket.on("data", onClientData.bind(connection));
		socket.on("error", onSocketError.bind(connection));
		socket.on("end", onClientFin.bind(connection));
		socket.on("close", onSocketClose.bind(connection));
		socket.on("timeout", onSocketTimeout.bind(connection));
	};

	onClientData = function (data) {
		this.clientBuffered = this.clientBuffered !== null ? Buffer.concat([this.clientBuffered, data], this.clientBuffered.length + data.length) : data;

		if (this.clientBuffered.length < CONST.FCGIHeaderLength) {
			return;
		}

		while (this.clientBuffered !== null) {
			let parsingData = this.clientBuffered, msgHeader = {}, msgContent = null;

			msgHeader.version = parsingData[CONST.FCGIVersionOffset];
			msgHeader.type = parsingData[CONST.FCGITypeOffset];
			msgHeader.reqId = parsingData.readUInt16BE(CONST.FCGIReqIdOffset);
			msgHeader.contentLength = parsingData.readUInt16BE(CONST.FCGIContentLengthOffset);
			msgHeader.paddingLength = parsingData[CONST.FCGIPaddingLengthOffset];

			if (parsingData.length < CONST.FCGIHeaderLength + msgHeader.contentLength + msgHeader.paddingLength) {
				this.clientBuffered = parsingData;
				return;
			} else if (parsingData.length > CONST.FCGIHeaderLength + msgHeader.contentLength + msgHeader.paddingLength) {
				this.clientBuffered = parsingData.slice(CONST.FCGIHeaderLength + msgHeader.contentLength + msgHeader.paddingLength);
			} else {
				this.clientBuffered = null;
			}

			if (! CONST.FCGIRecordTypes[msgHeader.type]) {
				sendFCGIRecord(this.socket, "FCGI_UNKNOWN_TYPE", 0, new Buffer([msgHeader.type, 0, 0, 0, 0, 0, 0, 0]));
				continue;
			}

			msgContent = parsingData.slice(CONST.FCGIHeaderLength, CONST.FCGIHeaderLength + msgHeader.contentLength);

			if (msgHeader.reqId === CONST.FCGINullReqId && CONST.FCGIRecordTypes[msgHeader.type] === "FCGI_GET_VALUES") {
				sendFCGIRecord(this.socket, "FCGI_GET_VALUES_RESULT", 0, new Buffer(0));
			} else if (CONST.FCGIRecordTypes[msgHeader.type] === "FCGI_BEGIN_REQUEST") {
				if (this.sessions[msgHeader.reqId] !== undefined) {
					sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_DUPLICATED_REQUEST");
				}

				if (msgContent.readUInt16BE(CONST.FCGIRoleOffset) !== CONST.FCGIRole) {
					sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_UNKNOWN_ROLE");
				}

				this.sessions[msgHeader.reqId] = {
					req : new ClientRequest(msgHeader.reqId, this.socket),
					res : new ServerResponse(msgHeader.reqId, this.socket),
					paramsEnded : false,
					bodyEnded : false,
				};

				if (msgContent[CONST.FCGIFlagsOffset] % 2 === 0) {
					this.sessions[msgHeader.reqId].res.closeSocketOnEnd = true;
				}
			} else {
				let session = this.sessions[msgHeader.reqId];

				if (session === undefined) {
					sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_UNOPENED_REQUEST");
					return;
				}

				switch (CONST.FCGIRecordTypes[msgHeader.type]) {
					case "FCGI_PARAMS":
						if (session.paramsEnded) {
							break;
						}

						if (msgContent.length === 0) {
							this.server.emit("request", session.req, session.res);
							session.paramsEnded = true;
							break;
						}

						for (let p = 0, s = 0, r = [0, 0, "", ""]; p < msgContent.length;) {
							switch (s) {
								case 0:
								case 1:
									r[s] += msgContent[p];

									if (msgContent[p] >= 0x80) {
										r[s] = msgContent.readUInt32BE(p) - 0x80000000;
										p += 4;
									} else {
										r[s] = msgContent[p];
										p += 1;
									}

									break;

								case 2:
								case 3:
									if (r[s % 2] !== 0) {
										r[s] = msgContent.slice(p, p += r[s % 2]).toString();
									}
									break;
							}

							s += 1;

							if (s === 4) {
								session.req.params[r[2]] = r[3];
								s = 0;
								r = [0, 0, "", ""];
							}
						}

						break;

					case "FCGI_STDIN":
					case "FCGI_DATA":
						if (session.bodyEnded) {
							break;
						}

						if (msgContent.length === 0) {
							session.req.emit("end");
							session.dataEnded = true;
						}

						session.req.emit("data", msgContent);

						break;
					case "FCGI_ABORT_REQUEST":
						sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_REQUEST_COMPLETE");
						return;

					default:
						sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_BAD_RECORD");
						return;
				}
			}
		}
	};

	onClientError = function (e, socket) {
		socket.destroy();
	};

	onClientFin = function () {
		if (this.clientPhase < 2) {
			this.server.emit("clientError", new Error("Client FIN before request completes"), this.socket);
		}
	};

	onSocketError = function (e) {
		this.server.emit("clientError", e, this.socket);
	};

	onSocketClose = function (hadError) {
		var self = this;

		self.socket.removeAllListeners();

		self.sessions.forEach(function (session) {
			session.req.emit("close");
			session.res.emit((hadError === false && self.socket._writableState.ended === true) ? "finish" : "close");
		});
	};

	onSocketTimeout = function () {
		this.server.emit("timeout", this.socket);

		process.nextTick(this.socket.end.bind(this.socket));
		process.nextTick(this.socket.destroy.bind(this.socket));
	};

	// Exporting
	exports.Server = Server;
	exports.ClientRequest = ClientRequest;
	exports.ServerResponse = ServerResponse;
	exports.createServer = createServer;
})();
<commit_msg>[fcgi] Fixed a bug in client data buffering (incorrect size check)<commit_after>// Copyright (C) 2014 Makoto Mizukami

(function () {
	"use strict";

	var DEFAULTS = {
		timeout : 180 * 1000	// Unit: second
	},

	CONST = {
		FCGIVersion : 1,
		FCGINullReqId : 0,
		FCGIRecordTypes : [
			undefined,
			"FCGI_BEGIN_REQUEST",
			"FCGI_ABORT_REQUEST",
			"FCGI_END_REQUEST",
			"FCGI_PARAMS",
			"FCGI_STDIN",
			"FCGI_STDOUT",
			"FCGI_STDERR",
			"FCGI_DATA",
			"FCGI_GET_VALUES",
			"FCGI_GET_VALUES_RESULT",
			"FCGI_UNKNOWN_TYPE"
		],
		FCGIEndReasons : [
			"FCGI_REQUEST_COMPLETE",
			"FCGI_CANT_MPX_CONN",
			"FCGI_OVERLOADED",
			"FCGI_UNKNOWN_ROLE"
		],
		FCGIRole : 1,
		FCGIHeaderLength : 8,
		FCGIVersionOffset : 0,
		FCGITypeOffset : 1,
		FCGIReqIdOffset : 2,
		FCGIContentLengthOffset : 4,
		FCGIPaddingLengthOffset : 6,
		FCGIRoleOffset : 0,
		FCGIFlagsOffset : 2,
	},

	// Classes
	Server, ClientRequest, ServerResponse,
	// Methods
	createServer,
	// Internal functions
	defaultStatusPhrase, sendFCGIRecord, sendFCGIEndRecord, onConnection, onClientData, onClientError, onClientFin, onSocketError, onSocketClose, onSocketTimeout,
	// Internal values
	CRLF = "\r\n", wellknownStatuses = require("./http_status.js").IANAStatuses,

	EE = require("events").EventEmitter, net = require("net"), util = require("util");

	Server = function (requestListener) {
		net.Server.call(this, { allowHalfOpen : true });

		if (typeof requestListener === typeof Function) {
			this.on("request", requestListener);
		}

		this.on("connection", onConnection);
		this.on("clientError", onClientError);
	};
	util.inherits(Server, net.Server);

	Server.prototype.timeout = DEFAULTS.timeout;

	Server.prototype.setTimeout = function (timeoutAfter, callback) {
		this.timeout = timeoutAfter;

		if (callback !== undefined) {
			this.on("timeout", callback);
		}
	};

	ClientRequest = function (id, socket) {
		EE.call(this);

		this.reqId = id;
		this.socket = socket;
		this.params = {};

		this.on("close", this.removeAllListeners);
	};
	util.inherits(ClientRequest, EE);

	ClientRequest.prototype.setTimeout = function (timeoutAfter, callback) {
		this.socket.setTimeout(timeoutAfter);

		if (callback !== undefined) {
			this.socket.on("timeout", callback);
		}
	};

	ServerResponse = function (id, socket) {
		var headerInfo = {
			fields : {},
			fieldQueue : [],
		};

		EE.call(this);

		this.reqId = id;
		this.socket = socket;
		this.closeSocketOnEnd = false;

		this.setHeader = ServerResponse.prototype.setHeader.bind(this, headerInfo);
		this.flushHeaders = ServerResponse.prototype.flushHeaders.bind(this, headerInfo);

		this.setStatus(200);

		this.on("close", this.removeAllListeners);
		this.on("finish", this.removeAllListeners);
	};
	util.inherits(ServerResponse, EE);

	ServerResponse.prototype.headerFinalized = false;

	ServerResponse.prototype.setStatus = function (sCode, reasonPhrase) {
		this.setHeader(["Status:", sCode.toString(), reasonPhrase !== undefined ? reasonPhrase : defaultStatusPhrase(sCode)].join(" "));
	};

	ServerResponse.prototype.setHeader = function (headerInfo, headerStatement) {
		var fieldName = headerStatement.split(":").shift().toLowerCase(),
		queued = headerInfo.fieldQueue.indexOf(fieldName);

		if (headerInfo.fields[fieldName] !== undefined && queued === -1) {
			return;
		}

		headerInfo.fields[fieldName] = headerStatement.trim();

		if (queued >= 0) {
			headerInfo.fieldQueue.splice(queued, 1);
		}

		headerInfo.fieldQueue.push(fieldName);
	};

	ServerResponse.prototype.flushHeaders = function (headerInfo, finalizeHeader) {
		if (this.socket.writable !== true || this.headerFinalized === true) {
			return;
		}

		if (headerInfo.fieldQueue.length > 0) {
			headerInfo.fieldQueue.forEach(function (field) {
				sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(headerInfo.fields[field] + CRLF));
			}.bind(this));
		}
		headerInfo.fieldQueue = [];

		if (finalizeHeader === true) {
			sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(CRLF));
			this.headerFinalized = true;
		}
	};

	ServerResponse.prototype.write = function (data) {
		this.headerFinalized !== true && this.flushHeaders(true);
		sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(data));
	};

	ServerResponse.prototype.end = function (data) {
		data !== undefined && this.write(data);
		sendFCGIRecord(this.socket, "FCGI_STDOUT", this.reqId, new Buffer(0));
		sendFCGIEndRecord(this.socket, this.reqId, "FCGI_REQUEST_COMPLETE");

		if (this.closeSocketOnEnd) {
			if (this.socket._writableState.writing === false) {
				this.socket.end();
			} else {
				this.socket.once("drain", this.socket.end);
			}
		}
	};

	createServer = function (requestListener) {
		return new Server(requestListener);
	};

	defaultStatusPhrase = function (sCode) {
		return wellknownStatuses[sCode] !== undefined ? wellknownStatuses[sCode] : [sCode.toString(), "Unknown Reason"].join(" ");
	};

	sendFCGIRecord = function (socket, type, reqId, content) {
		var msgHeader = new Buffer(CONST.FCGIHeaderLength);

		msgHeader.fill(0);
		msgHeader[CONST.FCGIVersionOffset] = CONST.FCGIVersion;
		msgHeader[CONST.FCGITypeOffset] = CONST.FCGIRecordTypes.indexOf(type);
		msgHeader.writeUInt16BE(reqId, CONST.FCGIReqIdOffset);
		msgHeader.writeUInt16BE(content.length, CONST.FCGIContentLengthOffset);
		msgHeader[CONST.FCGIPaddingLengthOffset] = 0;

		if (socket.writable === true) {
			socket.write(Buffer.concat([msgHeader, content], CONST.FCGIHeaderLength + content.length));
		}
	};

	sendFCGIEndRecord = function (socket, reqId, reason) {
		sendFCGIRecord(socket, "FCGI_END_REQUEST", reqId, new Buffer([0, 0, 0, 0, CONST.FCGIEndReasons.indexOf(reason), 0, 0, 0]));
	};

	onConnection = function (socket) {
		var connection = {
			server : this,
			socket : socket,
			clientBuffered : null,
			sessions : []
		};

		socket.setTimeout(this.timeout);

		socket.on("data", onClientData.bind(connection));
		socket.on("error", onSocketError.bind(connection));
		socket.on("end", onClientFin.bind(connection));
		socket.on("close", onSocketClose.bind(connection));
		socket.on("timeout", onSocketTimeout.bind(connection));
	};

	onClientData = function (data) {
		this.clientBuffered = this.clientBuffered !== null ? Buffer.concat([this.clientBuffered, data], this.clientBuffered.length + data.length) : data;

		while (this.clientBuffered !== null) {
			let parsingData = this.clientBuffered, msgHeader = {}, msgContent = null;

			if (parsingData.length < CONST.FCGIHeaderLength) {
				break;
			}

			msgHeader.version = parsingData[CONST.FCGIVersionOffset];
			msgHeader.type = parsingData[CONST.FCGITypeOffset];
			msgHeader.reqId = parsingData.readUInt16BE(CONST.FCGIReqIdOffset);
			msgHeader.contentLength = parsingData.readUInt16BE(CONST.FCGIContentLengthOffset);
			msgHeader.paddingLength = parsingData[CONST.FCGIPaddingLengthOffset];

			if (parsingData.length < CONST.FCGIHeaderLength + msgHeader.contentLength + msgHeader.paddingLength) {
				this.clientBuffered = parsingData;
				break;
			} else if (parsingData.length > CONST.FCGIHeaderLength + msgHeader.contentLength + msgHeader.paddingLength) {
				this.clientBuffered = parsingData.slice(CONST.FCGIHeaderLength + msgHeader.contentLength + msgHeader.paddingLength);
			} else {
				this.clientBuffered = null;
			}

			if (! CONST.FCGIRecordTypes[msgHeader.type]) {
				sendFCGIRecord(this.socket, "FCGI_UNKNOWN_TYPE", 0, new Buffer([msgHeader.type, 0, 0, 0, 0, 0, 0, 0]));
				continue;
			}

			msgContent = parsingData.slice(CONST.FCGIHeaderLength, CONST.FCGIHeaderLength + msgHeader.contentLength);

			if (msgHeader.reqId === CONST.FCGINullReqId && CONST.FCGIRecordTypes[msgHeader.type] === "FCGI_GET_VALUES") {
				sendFCGIRecord(this.socket, "FCGI_GET_VALUES_RESULT", 0, new Buffer(0));
			} else if (CONST.FCGIRecordTypes[msgHeader.type] === "FCGI_BEGIN_REQUEST") {
				if (this.sessions[msgHeader.reqId] !== undefined) {
					sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_DUPLICATED_REQUEST");
				}

				if (msgContent.readUInt16BE(CONST.FCGIRoleOffset) !== CONST.FCGIRole) {
					sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_UNKNOWN_ROLE");
				}

				this.sessions[msgHeader.reqId] = {
					req : new ClientRequest(msgHeader.reqId, this.socket),
					res : new ServerResponse(msgHeader.reqId, this.socket),
					paramsEnded : false,
					bodyEnded : false,
				};

				if (msgContent[CONST.FCGIFlagsOffset] % 2 === 0) {
					this.sessions[msgHeader.reqId].res.closeSocketOnEnd = true;
				}
			} else {
				let session = this.sessions[msgHeader.reqId];

				if (session === undefined) {
					sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_UNOPENED_REQUEST");
					return;
				}

				switch (CONST.FCGIRecordTypes[msgHeader.type]) {
					case "FCGI_PARAMS":
						if (session.paramsEnded) {
							break;
						}

						if (msgContent.length === 0) {
							this.server.emit("request", session.req, session.res);
							session.paramsEnded = true;
							break;
						}

						for (let p = 0, s = 0, r = [0, 0, "", ""]; p < msgContent.length;) {
							switch (s) {
								case 0:
								case 1:
									r[s] += msgContent[p];

									if (msgContent[p] >= 0x80) {
										r[s] = msgContent.readUInt32BE(p) - 0x80000000;
										p += 4;
									} else {
										r[s] = msgContent[p];
										p += 1;
									}

									break;

								case 2:
								case 3:
									if (r[s % 2] !== 0) {
										r[s] = msgContent.slice(p, p += r[s % 2]).toString();
									}
									break;
							}

							s += 1;

							if (s === 4) {
								session.req.params[r[2]] = r[3];
								s = 0;
								r = [0, 0, "", ""];
							}
						}

						break;

					case "FCGI_STDIN":
					case "FCGI_DATA":
						if (session.bodyEnded) {
							break;
						}

						if (msgContent.length === 0) {
							session.req.emit("end");
							session.dataEnded = true;
						}

						session.req.emit("data", msgContent);

						break;
					case "FCGI_ABORT_REQUEST":
						sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_REQUEST_COMPLETE");
						return;

					default:
						sendFCGIEndRecord(this.socket, msgHeader.reqId, "FCGI_BAD_RECORD");
						return;
				}
			}
		}
	};

	onClientError = function (e, socket) {
		socket.destroy();
	};

	onClientFin = function () {
		if (this.clientPhase < 2) {
			this.server.emit("clientError", new Error("Client FIN before request completes"), this.socket);
		}
	};

	onSocketError = function (e) {
		this.server.emit("clientError", e, this.socket);
	};

	onSocketClose = function (hadError) {
		var self = this;

		self.socket.removeAllListeners();

		self.sessions.forEach(function (session) {
			session.req.emit("close");
			session.res.emit((hadError === false && self.socket._writableState.ended === true) ? "finish" : "close");
		});
	};

	onSocketTimeout = function () {
		this.server.emit("timeout", this.socket);

		process.nextTick(this.socket.end.bind(this.socket));
		process.nextTick(this.socket.destroy.bind(this.socket));
	};

	// Exporting
	exports.Server = Server;
	exports.ClientRequest = ClientRequest;
	exports.ServerResponse = ServerResponse;
	exports.createServer = createServer;
})();
<|endoftext|>


<commit_before>const cp = require('child_process'),
      fs = require('fs'),
      FIFOError = require('./fifoError')

class FIFO {
    constructor(path) {
        this.path = path || this._generateFifoPath()

        this._initFifo()
        this.open = true
    }

    _initFifo() {
        let stat = this._statSafe(this.path)
        if (!stat) {
            this._createFifo()
        } else if (!stat.isFIFO()) {
            throw new FIFOError(this.path,
                    "Non fifo file with this name already exists.")
        } else {
            this.preserve = true
        }
    }

    _createFifo() {
        cp.execSync('mkfifo ' + this.path)
    }

    _statSafe(path) {
        try {
            return fs.statSync(path)
        } catch(e) {
            return false
        }
    }

    _generateFifoPath() {
        let path
        do {
            let random = Math.floor(Math.random() * 8999 + 1000)
            path = '/tmp/fifo-js-' + random + '.fifo'
        } while (this._statSafe(path))

        return path
    }

    setReader(reader) {
        this._throwIfClosed()

        this.reader = reader
        this._reader()
    }

    _reader() {
        fs.readFile(this.path, (err, data) => {
            if (this.open) {
                this.reader && this.reader(data.toString().trim())
                this._reader()
            }
        })
    }

    read(callback) {
        this._throwIfClosed()

        fs.readFile(this.path, (err, data) => {
            if (this.open && callback) {
                callback(data.toString().trim())
            }
        })
    }

    /**
     * Warning: Doesn't work in combination with fifo.write() since this locks
     * up the node process.
     */
    readSync() {
        this._throwIfClosed()

        return fs.readFileSync(this.path).toString().trim()
    }

    write(string, callback) {
        this._throwIfClosed()

        let writer = fs.createWriteStream(this.path, { autoClose: true })
        writer.end(string, () => {
            if (this.open && callback) {
                callback()
            }
        })
    }

    writeSync(string) {
        this._throwIfClosed()

        let child = cp.execSync('echo "' + string + '" > ' + this.path)
        return string
    }

    close() {
        this.open = false

        if (!this.preserve) {
            this._unlink()
        }
    }

    _unlink() {
        if (this._statSafe(this.path)) {
            fs.unlinkSync(this.path)
        }
    }

    _throwIfClosed() {
        if (!this.open) {
            throw new FIFOError(this.path, "Fifo isn't open.")
        }
    }

    get open() {
        return this._open && this._statSafe(this.path)
    }

    set open(value) {
        this._open = value
    }
}

module.exports = FIFO

<commit_msg>Renamed and improved _statSafe to _pathExists.<commit_after>const cp = require('child_process'),
      fs = require('fs'),
      FIFOError = require('./fifoError')

class FIFO {
    constructor(path) {
        this.path = path || this._generateFifoPath()

        this._initFifo()
        this.open = true
    }

    _initFifo() {
        let stat = this._pathExists()
        if (!stat) {
            this._createFifo()
        } else if (!stat.isFIFO()) {
            throw new FIFOError(this.path,
                    "Non fifo file with this name already exists.")
        } else {
            this.preserve = true
        }
    }

    _createFifo() {
        cp.execSync('mkfifo ' + this.path)
    }

    _pathExists(path) {
        path = path || this.path
        try {
            return fs.statSync(path)
        } catch(e) {
            return false
        }
    }

    _generateFifoPath() {
        let path
        do {
            let random = Math.floor(Math.random() * 8999 + 1000)
            path = '/tmp/fifo-js-' + random + '.fifo'
        } while (this._pathExists(path))

        return path
    }

    setReader(reader) {
        this._throwIfClosed()

        this.reader = reader
        this._reader()
    }

    _reader() {
        fs.readFile(this.path, (err, data) => {
            if (this.open) {
                this.reader && this.reader(data.toString().trim())
                this._reader()
            }
        })
    }

    read(callback) {
        this._throwIfClosed()

        fs.readFile(this.path, (err, data) => {
            if (this.open && callback) {
                callback(data.toString().trim())
            }
        })
    }

    /**
     * Warning: Doesn't work in combination with fifo.write() since this locks
     * up the node process.
     */
    readSync() {
        this._throwIfClosed()

        return fs.readFileSync(this.path).toString().trim()
    }

    write(string, callback) {
        this._throwIfClosed()

        let writer = fs.createWriteStream(this.path, { autoClose: true })
        writer.end(string, () => {
            if (this.open && callback) {
                callback()
            }
        })
    }

    writeSync(string) {
        this._throwIfClosed()

        let child = cp.execSync('echo "' + string + '" > ' + this.path)
        return string
    }

    close() {
        this.open = false

        if (!this.preserve) {
            this._unlink()
        }
    }

    _unlink() {
        if (this._pathExists()) {
            fs.unlinkSync(this.path)
        }
    }

    _throwIfClosed() {
        if (!this.open) {
            throw new FIFOError(this.path, "Fifo isn't open.")
        }
    }

    get open() {
        return this._open && this._pathExists()
    }

    set open(value) {
        this._open = value
    }
}

module.exports = FIFO

<|endoftext|>