<commit_before>const MBContract            = require('./mb_contract');
const MBDefaults            = require('./mb_defaults');
const MBNotifications       = require('./mb_notifications');
const MBPrice               = require('./mb_price');
const MBProcess             = require('./mb_process');
const MBTick                = require('./mb_tick');
const MBSymbols             = require('./mb_symbols');
const TradingAnalysis       = require('../trade/analysis');
const debounce              = require('../trade/common').debounce;
const Client                = require('../../base/client');
const Currency              = require('../../common/currency');
const onlyNumericOnKeypress = require('../../common/event_handler');
const localize              = require('../../../_common/localize').localize;
const State                 = require('../../../_common/storage').State;

/*
 * TradingEvents object contains all the event handler function required for
 * websocket trading page
 *
 * We need it as object so that we can call TradingEvent.init() only on trading
 * page for pjax to work else it will fire on all pages
 *
 */
const MBTradingEvents = (() => {
    const initiate = () => {
        const $form        = $('.trade_form');
        const hidden_class = 'invisible';
        const border_class = 'primary-border-color';
        const is_jp_client = Client.isJPClient();

        $(document).on('click', (e) => {
            if ($(e.target).parents('#payout_list').length) return;
            makeListsInvisible();
        });

        $form.find('.current, .header-current').on('click', function (e) {
            e.stopPropagation();
            const $this = $(this);
            let $list = $this.siblings('.list');
            if (!$list.length) {
                $list = $this.siblings().find('.list'); // in case of .header-current
            };
            if ($list.hasClass(hidden_class)) {
                makeListsInvisible();
            }
            $list.toggleClass(hidden_class);
            $this.toggleClass(border_class)
                .siblings().find('.current').toggleClass(border_class).end().end()
                .parent().siblings('.header-current').toggleClass(border_class);
        });

        /*
         * attach event to underlying change, event need to request new contract details and price
         */
        const $underlying = $form.find('#underlying');
        if ($underlying.length) {
            $underlying.on('click', '.list > div', function () {
                const underlying = $(this).attr('value');
                MBContract.setCurrentItem($underlying, underlying, 1);
                MBDefaults.set('underlying', underlying);
                MBNotifications.hide('SYMBOL_INACTIVE');

                MBTick.clean();

                MBProcess.getContracts(underlying);
                // forget the old tick id i.e. close the old tick stream
                MBProcess.processForgetTicks().then(() => {
                    // get ticks for current underlying
                    MBTick.request(underlying);
                });
            });
        }

        const $category = $form.find('#category');
        if ($category.length) {
            $category.on('click', '.list > div', function () {
                const category = $(this).attr('value');
                MBContract.setCurrentItem($category, category);
                MBDefaults.set('category', category);
                MBContract.populatePeriods(1);
                MBProcess.processPriceRequest();
                TradingAnalysis.request();
            });
        }

        const $period = $form.find('#period');
        if ($period.length) {
            $period.on('click', '.list > div', function () {
                const period = $(this).attr('value');
                MBContract.setCurrentItem($period, period);
                MBDefaults.set('period', period);
                MBProcess.processPriceRequest();
                $('.remaining-time').removeClass('alert');
                MBContract.displayRemainingTime(true, is_jp_client);
            });
            if (!is_jp_client) {
                const $header_cur = $form.find('.header-current');
                $period.on('mouseover', (e) => {
                    e.stopPropagation();
                    $header_cur.addClass(border_class);
                }).on('mouseleave', (e) => {
                    e.stopPropagation();
                    if ($period.find('.list').hasClass(hidden_class)) {
                        $header_cur.removeClass(border_class);
                    }
                });
            }
        }

        const validatePayout = (payout_amount, $error_wrapper) => {
            const market              = MBSymbols.getAllSymbols()[MBDefaults.get('underlying')].market;
            const selected_currency   = MBDefaults.get('currency');
            const max_client_amount   = State.getResponse(`landing_company.financial_company.currency_config.${market}.${selected_currency}.max_payout`) || 5000;

            let is_valid  = true;
            let error_msg = '';

            if (!payout_amount || isNaN(payout_amount)) {
                is_valid  = false;
                error_msg = localize('Should be a valid number.');
            } else if (+payout_amount > max_client_amount) {
                is_valid  = false;
                error_msg = localize('Should be below [_1]', [max_client_amount]);
            }

            // if value has decimal places
            if (is_valid && +payout_amount % 1 !== 0) {
                const allowed_decimals = Currency.getDecimalPlaces(MBDefaults.get('currency'));

                // verify number of decimal places doesn't exceed the allowed decimal places according to the currency
                is_valid = payout_amount.toString().replace(/^-?\d*\.?|0+$/, '').length <= allowed_decimals;
                if (!is_valid) {
                    error_msg = localize('Up to [_1] decimal places are allowed.', [allowed_decimals]);
                }
            }

            if (!is_valid && $error_wrapper && error_msg) {
                const $err_payout = $('#err_payout');
                if ($err_payout.length) {
                    $err_payout.text(error_msg);
                } else {
                    $error_wrapper.append($('<p/>', { class: 'error-msg gr-row', id: 'err_payout', text: error_msg }));
                }
            }

            return is_valid;
        };

        const $payout = $form.find('#payout');
        if ($payout.length) {
            const $payout_list = $form.find('#payout_list');

            const appendActualPayout = (payout) => {
                $payout.find('.current').append($('<div/>', { class: 'hint', text: localize('Payout') }).append($('<span/>', { id: 'actual_payout', html: Currency.formatMoney('JPY', payout * 1000) })));
            };

            const client_currency = Client.get('currency') || MBDefaults.get('currency');
            const is_crypto       = Currency.isCryptocurrency(client_currency);
            let old_value         = Currency.getMinPayout(client_currency);
            if (!$payout.attr('value')) {
                const amount   = `payout${is_crypto ? '_crypto' : ''}`;
                let payout_def = MBDefaults.get(amount);
                if (!validatePayout(payout_def)) {
                    payout_def = old_value;
                }
                $payout.value = payout_def;
                MBDefaults.set(amount, payout_def);
                $payout.attr('value', payout_def);
                if (is_jp_client) {
                    $payout.find('.current').html(payout_def);
                    appendActualPayout(payout_def);
                }
            }
            if (is_jp_client) {
                $payout.find('.current').on('click', function () {
                    old_value      = +this.childNodes[0].nodeValue;
                    const $list    = $(`#${$(this).parent().attr('id')}_list`);
                    const $sublist = $list.find('.list');
                    if ($list.hasClass(hidden_class)) {
                        makeListsInvisible();
                    }
                    $list.toggleClass(hidden_class);
                    $sublist.toggleClass(hidden_class);
                    $category.toggleClass(hidden_class);
                    $period.toggleClass(hidden_class);
                });
            } else {
                const $panel = $('#panel');
                $payout
                    .on('click', function() { $(this).select(); })
                    .on('keypress', onlyNumericOnKeypress)
                    .on('input', debounce((e) => {
                        const payout   = e.target.value;
                        const currency = MBDefaults.get('currency');
                        if (validatePayout(payout, $panel)) {
                            $panel.find('#err_payout').remove();
                            $payout.removeClass('error');
                            e.target.setAttribute('value', payout);
                            MBDefaults.set(`payout${Currency.isCryptocurrency(currency) ? '_crypto' : ''}`, payout);
                            MBProcess.processPriceRequest();
                        } else {
                            $payout.addClass('error');
                            MBPrice.showPriceOverlay();
                        }
                    }));
            }
            if ($payout_list.length) {
                $payout_list.on('click', '> .list > div', debounce(function () {
                    const payout = +MBDefaults.get(`payout${Currency.isCryptocurrency(MBDefaults.get('currency')) ? '_crypto' : ''}`);
                    const value  = $(this).attr('value');
                    let new_payout;
                    if (/\+|-/.test(value)) {
                        new_payout = payout + parseInt(value);
                        if (new_payout < 1 && is_jp_client) {
                            new_payout = 1;
                        }
                    } else if (/ok|cancel/.test(value)) {
                        if (value === 'cancel') new_payout = old_value || 10;
                        makeListsInvisible();
                    } else {
                        new_payout = value;
                    }

                    if (validatePayout(new_payout)) {
                        $('.price-table').setVisibility(1);
                        MBDefaults.set('payout', new_payout);
                        $payout.attr('value', new_payout).find('.current').html(new_payout);
                        if (is_jp_client) {
                            appendActualPayout(new_payout);
                        }
                        MBProcess.processPriceRequest();
                    }
                }));
                $payout_list.find('div[unselectable]').on('selectstart mousedown', () => false);
            }
        }

        const $currency = $form.find('#currency');
        if ($currency.length) {
            $currency.on('click', '.list > div', function () {
                const currency = $(this).attr('value');
                MBContract.setCurrentItem($currency, currency);
                MBDefaults.set('currency', currency);
                if (is_jp_client) {
                    MBProcess.processPriceRequest();
                } else {
                    const is_crypto = Currency.isCryptocurrency(currency);
                    let amount      = MBDefaults.get(`payout${is_crypto ? '_crypto' : ''}`);
                    if (!amount) {
                        amount = Currency.getMinPayout(currency);
                        MBDefaults.set(`payout${is_crypto ? '_crypto' : ''}`, amount);
                    }
                    $payout
                        .val(amount).attr('value', amount)
                        .trigger('input'); // payout will call processPriceRequest
                }
            });
        }

        const $trading_status   = $('.trading-status');
        const $allow_trading    = $trading_status.find('#allow');
        const $disallow_trading = $trading_status.find('#disallow');
        const setTradingStatus  = (is_enabled) => {
            if (is_enabled) {
                MBPrice.hidePriceOverlay();
                MBNotifications.hide('TRADING_DISABLED');
                $disallow_trading.removeClass('selected');
                $allow_trading.addClass('selected');
            } else {
                MBPrice.showPriceOverlay();
                $allow_trading.removeClass('selected');
                $disallow_trading.addClass('selected');
            }
        };
        if ($trading_status.length) {
            setTradingStatus(0);
            $trading_status.on('click', (e) => {
                const status = e.target.getAttribute('id');
                MBDefaults.set('disable_trading', status === 'disallow');
                setTradingStatus(status === 'allow');
            });
        }

        const makeListsInvisible = () => {
            $form.find('.list, #payout_list').setVisibility(0).end()
                .find('#period, #category')
                .setVisibility(1);
            $form.find('.current, .header-current').removeClass(border_class);
        };
    };

    return {
        init: initiate,
    };
})();

module.exports = MBTradingEvents;
<commit_msg>Change validation string<commit_after>const MBContract            = require('./mb_contract');
const MBDefaults            = require('./mb_defaults');
const MBNotifications       = require('./mb_notifications');
const MBPrice               = require('./mb_price');
const MBProcess             = require('./mb_process');
const MBTick                = require('./mb_tick');
const MBSymbols             = require('./mb_symbols');
const TradingAnalysis       = require('../trade/analysis');
const debounce              = require('../trade/common').debounce;
const Client                = require('../../base/client');
const Currency              = require('../../common/currency');
const onlyNumericOnKeypress = require('../../common/event_handler');
const localize              = require('../../../_common/localize').localize;
const State                 = require('../../../_common/storage').State;

/*
 * TradingEvents object contains all the event handler function required for
 * websocket trading page
 *
 * We need it as object so that we can call TradingEvent.init() only on trading
 * page for pjax to work else it will fire on all pages
 *
 */
const MBTradingEvents = (() => {
    const initiate = () => {
        const $form        = $('.trade_form');
        const hidden_class = 'invisible';
        const border_class = 'primary-border-color';
        const is_jp_client = Client.isJPClient();

        $(document).on('click', (e) => {
            if ($(e.target).parents('#payout_list').length) return;
            makeListsInvisible();
        });

        $form.find('.current, .header-current').on('click', function (e) {
            e.stopPropagation();
            const $this = $(this);
            let $list = $this.siblings('.list');
            if (!$list.length) {
                $list = $this.siblings().find('.list'); // in case of .header-current
            };
            if ($list.hasClass(hidden_class)) {
                makeListsInvisible();
            }
            $list.toggleClass(hidden_class);
            $this.toggleClass(border_class)
                .siblings().find('.current').toggleClass(border_class).end().end()
                .parent().siblings('.header-current').toggleClass(border_class);
        });

        /*
         * attach event to underlying change, event need to request new contract details and price
         */
        const $underlying = $form.find('#underlying');
        if ($underlying.length) {
            $underlying.on('click', '.list > div', function () {
                const underlying = $(this).attr('value');
                MBContract.setCurrentItem($underlying, underlying, 1);
                MBDefaults.set('underlying', underlying);
                MBNotifications.hide('SYMBOL_INACTIVE');

                MBTick.clean();

                MBProcess.getContracts(underlying);
                // forget the old tick id i.e. close the old tick stream
                MBProcess.processForgetTicks().then(() => {
                    // get ticks for current underlying
                    MBTick.request(underlying);
                });
            });
        }

        const $category = $form.find('#category');
        if ($category.length) {
            $category.on('click', '.list > div', function () {
                const category = $(this).attr('value');
                MBContract.setCurrentItem($category, category);
                MBDefaults.set('category', category);
                MBContract.populatePeriods(1);
                MBProcess.processPriceRequest();
                TradingAnalysis.request();
            });
        }

        const $period = $form.find('#period');
        if ($period.length) {
            $period.on('click', '.list > div', function () {
                const period = $(this).attr('value');
                MBContract.setCurrentItem($period, period);
                MBDefaults.set('period', period);
                MBProcess.processPriceRequest();
                $('.remaining-time').removeClass('alert');
                MBContract.displayRemainingTime(true, is_jp_client);
            });
            if (!is_jp_client) {
                const $header_cur = $form.find('.header-current');
                $period.on('mouseover', (e) => {
                    e.stopPropagation();
                    $header_cur.addClass(border_class);
                }).on('mouseleave', (e) => {
                    e.stopPropagation();
                    if ($period.find('.list').hasClass(hidden_class)) {
                        $header_cur.removeClass(border_class);
                    }
                });
            }
        }

        const validatePayout = (payout_amount, $error_wrapper) => {
            const market              = MBSymbols.getAllSymbols()[MBDefaults.get('underlying')].market;
            const selected_currency   = MBDefaults.get('currency');
            const max_client_amount   = State.getResponse(`landing_company.financial_company.currency_config.${market}.${selected_currency}.max_payout`) || 5000;

            let is_valid  = true;
            let error_msg = '';

            if (!payout_amount || isNaN(payout_amount)) {
                is_valid  = false;
                error_msg = localize('Should be a valid number.');
            } else if (+payout_amount > max_client_amount) {
                is_valid  = false;
                error_msg = localize('Should be less than [_1]', [max_client_amount]);
            }

            // if value has decimal places
            if (is_valid && +payout_amount % 1 !== 0) {
                const allowed_decimals = Currency.getDecimalPlaces(MBDefaults.get('currency'));

                // verify number of decimal places doesn't exceed the allowed decimal places according to the currency
                is_valid = payout_amount.toString().replace(/^-?\d*\.?|0+$/, '').length <= allowed_decimals;
                if (!is_valid) {
                    error_msg = localize('Up to [_1] decimal places are allowed.', [allowed_decimals]);
                }
            }

            if (!is_valid && $error_wrapper && error_msg) {
                const $err_payout = $('#err_payout');
                if ($err_payout.length) {
                    $err_payout.text(error_msg);
                } else {
                    $error_wrapper.append($('<p/>', { class: 'error-msg gr-row', id: 'err_payout', text: error_msg }));
                }
            }

            return is_valid;
        };

        const $payout = $form.find('#payout');
        if ($payout.length) {
            const $payout_list = $form.find('#payout_list');

            const appendActualPayout = (payout) => {
                $payout.find('.current').append($('<div/>', { class: 'hint', text: localize('Payout') }).append($('<span/>', { id: 'actual_payout', html: Currency.formatMoney('JPY', payout * 1000) })));
            };

            const client_currency = Client.get('currency') || MBDefaults.get('currency');
            const is_crypto       = Currency.isCryptocurrency(client_currency);
            let old_value         = Currency.getMinPayout(client_currency);
            if (!$payout.attr('value')) {
                const amount   = `payout${is_crypto ? '_crypto' : ''}`;
                let payout_def = MBDefaults.get(amount);
                if (!validatePayout(payout_def)) {
                    payout_def = old_value;
                }
                $payout.value = payout_def;
                MBDefaults.set(amount, payout_def);
                $payout.attr('value', payout_def);
                if (is_jp_client) {
                    $payout.find('.current').html(payout_def);
                    appendActualPayout(payout_def);
                }
            }
            if (is_jp_client) {
                $payout.find('.current').on('click', function () {
                    old_value      = +this.childNodes[0].nodeValue;
                    const $list    = $(`#${$(this).parent().attr('id')}_list`);
                    const $sublist = $list.find('.list');
                    if ($list.hasClass(hidden_class)) {
                        makeListsInvisible();
                    }
                    $list.toggleClass(hidden_class);
                    $sublist.toggleClass(hidden_class);
                    $category.toggleClass(hidden_class);
                    $period.toggleClass(hidden_class);
                });
            } else {
                const $panel = $('#panel');
                $payout
                    .on('click', function() { $(this).select(); })
                    .on('keypress', onlyNumericOnKeypress)
                    .on('input', debounce((e) => {
                        const payout   = e.target.value;
                        const currency = MBDefaults.get('currency');
                        if (validatePayout(payout, $panel)) {
                            $panel.find('#err_payout').remove();
                            $payout.removeClass('error');
                            e.target.setAttribute('value', payout);
                            MBDefaults.set(`payout${Currency.isCryptocurrency(currency) ? '_crypto' : ''}`, payout);
                            MBProcess.processPriceRequest();
                        } else {
                            $payout.addClass('error');
                            MBPrice.showPriceOverlay();
                        }
                    }));
            }
            if ($payout_list.length) {
                $payout_list.on('click', '> .list > div', debounce(function () {
                    const payout = +MBDefaults.get(`payout${Currency.isCryptocurrency(MBDefaults.get('currency')) ? '_crypto' : ''}`);
                    const value  = $(this).attr('value');
                    let new_payout;
                    if (/\+|-/.test(value)) {
                        new_payout = payout + parseInt(value);
                        if (new_payout < 1 && is_jp_client) {
                            new_payout = 1;
                        }
                    } else if (/ok|cancel/.test(value)) {
                        if (value === 'cancel') new_payout = old_value || 10;
                        makeListsInvisible();
                    } else {
                        new_payout = value;
                    }

                    if (validatePayout(new_payout)) {
                        $('.price-table').setVisibility(1);
                        MBDefaults.set('payout', new_payout);
                        $payout.attr('value', new_payout).find('.current').html(new_payout);
                        if (is_jp_client) {
                            appendActualPayout(new_payout);
                        }
                        MBProcess.processPriceRequest();
                    }
                }));
                $payout_list.find('div[unselectable]').on('selectstart mousedown', () => false);
            }
        }

        const $currency = $form.find('#currency');
        if ($currency.length) {
            $currency.on('click', '.list > div', function () {
                const currency = $(this).attr('value');
                MBContract.setCurrentItem($currency, currency);
                MBDefaults.set('currency', currency);
                if (is_jp_client) {
                    MBProcess.processPriceRequest();
                } else {
                    const is_crypto = Currency.isCryptocurrency(currency);
                    let amount      = MBDefaults.get(`payout${is_crypto ? '_crypto' : ''}`);
                    if (!amount) {
                        amount = Currency.getMinPayout(currency);
                        MBDefaults.set(`payout${is_crypto ? '_crypto' : ''}`, amount);
                    }
                    $payout
                        .val(amount).attr('value', amount)
                        .trigger('input'); // payout will call processPriceRequest
                }
            });
        }

        const $trading_status   = $('.trading-status');
        const $allow_trading    = $trading_status.find('#allow');
        const $disallow_trading = $trading_status.find('#disallow');
        const setTradingStatus  = (is_enabled) => {
            if (is_enabled) {
                MBPrice.hidePriceOverlay();
                MBNotifications.hide('TRADING_DISABLED');
                $disallow_trading.removeClass('selected');
                $allow_trading.addClass('selected');
            } else {
                MBPrice.showPriceOverlay();
                $allow_trading.removeClass('selected');
                $disallow_trading.addClass('selected');
            }
        };
        if ($trading_status.length) {
            setTradingStatus(0);
            $trading_status.on('click', (e) => {
                const status = e.target.getAttribute('id');
                MBDefaults.set('disable_trading', status === 'disallow');
                setTradingStatus(status === 'allow');
            });
        }

        const makeListsInvisible = () => {
            $form.find('.list, #payout_list').setVisibility(0).end()
                .find('#period, #category')
                .setVisibility(1);
            $form.find('.current, .header-current').removeClass(border_class);
        };
    };

    return {
        init: initiate,
    };
})();

module.exports = MBTradingEvents;
<|endoftext|>


<commit_before>const util = require('util')
const request = util.promisify(require('request'))
const randomstring = require('randomstring')

module.exports = async (Joi) => {
  const useHttps = (process.env.USE_HTTPS)
    ? (process.env.USE_HTTPS.toLowerCase() !== 'false')
    : true

  const clientId = (process.env.KIBANA_OAUTH2_CLIENT_ID)
    ? process.env.KIBANA_OAUTH2_CLIENT_ID
    : 'client_id'

  const clientSecret = (process.env.KIBANA_OAUTH2_CLIENT_SECRET)
    ? process.env.KIBANA_OAUTH2_CLIENT_SECRET
    : 'client_secret'

  const skipSslValidation = (process.env.SKIP_SSL_VALIDATION)
    ? (process.env.SKIP_SSL_VALIDATION.toLowerCase() === 'true')
    : false

  const cfSystemOrg = (process.env.CF_SYSTEM_ORG) ? process.env.CF_SYSTEM_ORG : 'system'

  // TODO remove default value for CF_API_URI
  const cloudFoundryApiUri = (process.env.CF_API_URI)
    ? process.env.CF_API_URI.replace(/\/$/, '')
    : 'https://api.cf03.mydeploy.xyz'

  const logoutRedirectUri = (process.env.KIBANA_DOMAIN)
    ? ((useHttps)
      ? 'https://' : 'http://') + (process.env.KIBANA_DOMAIN ? process.env.KIBANA_DOMAIN : 'localhost:5601') + '/login' : ''

  const useRedisSessions = (process.env.REDIS_HOST) ? true : false
  const redisHost = (process.env.REDIS_HOST) ? process.env.REDIS_HOST : '127.0.0.1'
  const redisPort = (process.env.REDIS_PORT) ? process.env.REDIS_PORT : '6379'
  const cfInfoUri = cloudFoundryApiUri + '/v2/info'
  const sessionExpirationMs = (process.env.SESSION_EXPIRATION_MS)
    ? process.env.SESSION_EXPIRATION_MS
    : 12 * 60 * 60 * 1000 // 12 hours by default
  const randomString = process.env.SESSION_KEY || randomstring.generate(40)
  const skipAuthorization = process.env.SKIP_AUTHORIZATION

  if (skipSslValidation) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
  }
  try {
    // Fetch location of login server, then set config
    const response = await request(cfInfoUri)

    const cfInfo = JSON.parse(response.body)
    const result = Joi.object({
      enabled: Joi.boolean().default(true),
      client_id: Joi.string().default(clientId),
      client_secret: Joi.string().default(clientSecret),
      skip_ssl_validation: Joi.boolean().default(skipSslValidation),
      cf_system_org: Joi.string().default(cfSystemOrg),
      authorization_uri: Joi.string().default(cfInfo.authorization_endpoint + '/oauth/authorize'),
      logout_uri: Joi.string().default(cfInfo.authorization_endpoint + '/logout.do' +
        /*
          Set 'redirect' parameter
          if logout_redirect_uri property is set to get back to Kibana app after logout.
          (note that redirects after logout
          should be also enabled
          in UAA - e.g. https://github.com/cloudfoundry/uaa/blob/3.9.3/uaa/src/main/resources/login.yml#L38-L45)
        */
        ((logoutRedirectUri !== '') ? '?redirect=' + logoutRedirectUri : '')),
      token_uri: Joi.string().default(cfInfo.token_endpoint + '/oauth/token'),
      account_info_uri: Joi.string().default(cfInfo.token_endpoint + '/userinfo'),
      organizations_uri: Joi.string().default(cloudFoundryApiUri + '/v2/organizations'),
      spaces_uri: Joi.string().default(cloudFoundryApiUri + '/v2/spaces'),
      random_passphrase: Joi.string().default(randomString),
      use_redis_sessions: Joi.boolean().default(useRedisSessions),
      redis_host: Joi.string().default(redisHost),
      redis_port: Joi.string().default(redisPort),
      session_expiration_ms: Joi.number().integer().default(sessionExpirationMs),
      use_https: Joi.boolean().default(useHttps),
      skip_authorization: Joi.boolean().default(skipAuthorization)
    }).default()

    return result
  } catch (error) {
    console.error(`config.ERROR fetching CF info from "${cfInfoUri}`, error)

    return Joi.object({
      enabled: Joi.boolean().default(true)
    }).default()
  }
}
<commit_msg>kibana.plugin cf-authentication update to v7.6.1<commit_after>const util = require('util')
const request = util.promisify(require('request'))
const randomstring = require('randomstring')

module.exports = async (Joi) => {
  const useHttps = (process.env.USE_HTTPS)
    ? (process.env.USE_HTTPS.toLowerCase() !== 'false')
    : true

  const clientId = (process.env.KIBANA_OAUTH2_CLIENT_ID)
    ? process.env.KIBANA_OAUTH2_CLIENT_ID
    : 'client_id'

  const clientSecret = (process.env.KIBANA_OAUTH2_CLIENT_SECRET)
    ? process.env.KIBANA_OAUTH2_CLIENT_SECRET
    : 'client_secret'

  const skipSslValidation = (process.env.SKIP_SSL_VALIDATION)
    ? (process.env.SKIP_SSL_VALIDATION.toLowerCase() === 'true')
    : false

  const cfSystemOrg = (process.env.CF_SYSTEM_ORG) ? process.env.CF_SYSTEM_ORG : 'system'

  // TODO remove default value for CF_API_URI
  const cloudFoundryApiUri = (process.env.CF_API_URI)
    ? process.env.CF_API_URI.replace(/\/$/, '')
    : 'https://api.cf03.mydeploy.xyz'

  const logoutRedirectUri = (process.env.KIBANA_DOMAIN)
    ? ((useHttps)
      ? 'https://' : 'http://') + (process.env.KIBANA_DOMAIN ? process.env.KIBANA_DOMAIN : 'localhost:5601') + '/login' : ''

  const useRedisSessions = (process.env.REDIS_HOST) ? true : false
  const redisHost = (process.env.REDIS_HOST) ? process.env.REDIS_HOST : '127.0.0.1'
  const redisPort = (process.env.REDIS_PORT) ? process.env.REDIS_PORT : '6379'
  const cfInfoUri = cloudFoundryApiUri + '/v2/info'
  const sessionExpirationMs = (process.env.SESSION_EXPIRATION_MS)
    ? process.env.SESSION_EXPIRATION_MS
    : 12 * 60 * 60 * 1000 // 12 hours by default
  const randomString = process.env.SESSION_KEY || randomstring.generate(40)
  const skipAuthorization = process.env.SKIP_AUTHORIZATION

  if (skipSslValidation) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
  }
  try {
    // Fetch location of login server, then set config
    const response = await request(cfInfoUri)

    const cfInfo = JSON.parse(response.body)
    const result = Joi.object({
      enabled: Joi.boolean().default(true),
      client_id: Joi.string().default(clientId),
      client_secret: Joi.string().default(clientSecret),
      skip_ssl_validation: Joi.boolean().default(skipSslValidation),
      cf_system_org: Joi.string().default(cfSystemOrg),
      authorization_uri: Joi.string().default(cfInfo.authorization_endpoint + '/oauth/authorize'),
      logout_uri: Joi.string().default(cfInfo.authorization_endpoint + '/logout.do' +
        /*
          Set 'redirect' parameter
          if logout_redirect_uri property is set to get back to Kibana app after logout.
          (note that redirects after logout
          should be also enabled
          in UAA - e.g. https://github.com/cloudfoundry/uaa/blob/3.9.3/uaa/src/main/resources/login.yml#L38-L45)
        */
        ((logoutRedirectUri !== '') ? '?redirect=' + logoutRedirectUri : '')),
      token_uri: Joi.string().default(cfInfo.token_endpoint + '/oauth/token'),
      account_info_uri: Joi.string().default(cfInfo.token_endpoint + '/userinfo'),
      organizations_uri: Joi.string().default(cloudFoundryApiUri + '/v2/organizations'),
      spaces_uri: Joi.string().default(cloudFoundryApiUri + '/v2/spaces'),
      random_passphrase: Joi.string().default(randomString),
      use_redis_sessions: Joi.boolean().default(useRedisSessions),
      redis_host: Joi.string().default(redisHost),
      redis_port: Joi.string().default(redisPort),
      session_expiration_ms: Joi.number().integer().default(sessionExpirationMs),
      use_https: Joi.boolean().default(useHttps),
      skip_authorization: Joi.boolean().default(skipAuthorization)
    }).default()

    return result
  } catch (error) {
    let message = `config.ERROR fetching CF info from "${cfInfoUri}". `;

    message +=
      error.message === "self signed certificate"
        ? "Self signed certificate detected. Please set kibana-auth.cloudfoundry.skip_ssl_validation=true in kibana-auth-plugin.yml if this is desired behavior"
        : error.message;

    throw new Error(message);
  }
}
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { PrescriptionSummary } from '../PrescriptionSummary';
import { PrescriptionInfo } from '../PrescriptionInfo';
import { FlexView } from '../FlexView';
import { PageButton } from '../PageButton';

import { FinaliseActions } from '../../actions/FinaliseActions';

const mapStateToProps = state => {
  const { prescription, patient, prescriber } = state;
  return { ...prescription, ...patient, ...prescriber };
};

const mapDispatchToProps = dispatch => {
  const openFinaliseModal = () => dispatch(FinaliseActions.openModal());
  return { openFinaliseModal };
};

const PrescriptionConfirmationComponent = ({ transaction, openFinaliseModal }) => (
  <FlexView flex={1}>
    <PrescriptionInfo />
    <PrescriptionSummary transaction={transaction} />
    <PageButton style={{ alignSelf: 'flex-end' }} text="Complete" onPress={openFinaliseModal} />
  </FlexView>
);

PrescriptionConfirmationComponent.propTypes = {
  transaction: PropTypes.object.isRequired,
  openFinaliseModal: PropTypes.func.isRequired,
};

export const PrescriptionConfirmation = connect(
  mapStateToProps,
  mapDispatchToProps
)(PrescriptionConfirmationComponent);
<commit_msg>Add updated PrescriptionConfirmation<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { PrescriptionSummary } from '../PrescriptionSummary';
import { PrescriptionInfo } from '../PrescriptionInfo';
import { FlexView } from '../FlexView';
import { PageButton } from '../PageButton';
import { FlexRow } from '../FlexRow';

import { UIDatabase } from '../../database/index';
import { pay } from '../../utilities/modules/dispensary/pay';
import { FinaliseActions } from '../../actions/FinaliseActions';
import { PaymentSummary } from '../PaymentSummary';

const mapStateToProps = state => {
  const { prescription, patient, prescriber, user, payment } = state;
  const { cashAmount } = payment;

  return { ...prescription, ...patient, ...prescriber, ...user, cashAmount };
};

const mapDispatchToProps = dispatch => {
  const openFinaliseModal = () => dispatch(FinaliseActions.openModal());
  return { openFinaliseModal };
};

const PrescriptionConfirmationComponent = ({
  transaction,
  currentUser,
  currentPatient,
  paymentAmount,
}) => {
  const confirmPrescription = React.useCallback(
    () => UIDatabase.write(() => pay(currentUser, currentPatient, transaction, paymentAmount)),
    []
  );
  return (
    <FlexView flex={1}>
      <PrescriptionInfo />
      <FlexRow flex={1}>
        <PrescriptionSummary transaction={transaction} />
        <PaymentSummary />
      </FlexRow>
      <PageButton style={{ alignSelf: 'flex-end' }} text="Complete" onPress={confirmPrescription} />
    </FlexView>
  );
};

PrescriptionConfirmationComponent.propTypes = {
  transaction: PropTypes.object.isRequired,
  currentUser: PropTypes.object.isRequired,
  currentPatient: PropTypes.object.isRequired,
  paymentAmount: PropTypes.object.isRequired,
};

export const PrescriptionConfirmation = connect(
  mapStateToProps,
  mapDispatchToProps
)(PrescriptionConfirmationComponent);
<|endoftext|>


<commit_before>/*jslint evil: true*/ // Get off my back, Mr. Crockford. Lazy workers need eval.
(function (globals) {

    /**
     * Constructor for new lazy-workers.
     * @param scriptUrl url of the script containing the worker source
     */
    function Worker(scriptUrl) {
        var self = this,
            wrapper;

        self.lazy = true;

        /**
         * Constructor for ErrorEvent objects.
         * @param type a string representing the error type
         */
        function ErrorEvent(type) {
            this.type = type;
            this.bubbles = false;
            this.cancelBubble = false;
            this.cancelable = false;
            this.defaultPrevented = false;
            this.eventPhase = 0;
            this.filename = '';
            this.lineno = 0;
            this.returnValue = true;
            this.timestamp = (new Date()).getTime();
        }

        /**
         * Generates an ErrorEvent with the given message and calls the worker
         * wrapper's onerror function.
         * @param errorMessage
         */
        function handleError(errorMessage) {
            var errorEvent;

            if (typeof wrapper.onerror === 'function') {
                errorEvent = new ErrorEvent('error');
                errorEvent.message = errorMessage;
                wrapper.onerror(errorEvent);
            }
        }

        /**
         * Initializes the worker by eval-ing the worker's source.
         */
        function init() {
            fetchScript(scriptUrl, function(request) {
                evalWorkerScript(request.responseText);
            });
        }

        /**
         * Fetches the worker source synchronously for the given url and calls
         * the provided function with the resulting XMLHttpRequest object.
         * @param url
         * @param f
         */
        function fetchScript(url, f) {
            var request = new XMLHttpRequest();

            request.open('GET', url, false);
            request.onload = function() {
                f(request);
            };
            request.send();
        }

        /**
         * Evals the given worker source string and sets up any worker-global
         * variables needed.
         * @param src
         */
        function evalWorkerScript(src) {
            // Make importScripts available to eval'ed code
            var importScripts = self.importScripts;

            try {
                eval(src);
            } catch (ex) {
                handleError('Error in worker script: ' + ex.message);
            }
        }

        /**
         * Posts a message to the main thread (the worker instance's onmessage
         * function in the case of lazy-worker).
         * @param msg
         */
        self.postMessage = function(msg) {
            if (typeof wrapper.onmessage === 'function') {
                try {
                    wrapper.onmessage({data: msg});
                } catch (ex) {
                    handleError('Error on onmessage handler: ' + ex.message);
                }
            }
        };

        /**
         * Implementation of the web worker importScripts function that loads
         * scripts synchronously and evals them.
         * @param script one or more string urls
         */
        self.importScripts = function(script) {
            var scripts,
                i,
                len;

            if (arguments.length > 1) {
                scripts = arguments;
            } else {
                scripts = [script];
            }

            function handleRequest(request) {
                var scriptSource = request.responseText;

                try {
                    eval(scriptSource);
                } catch (ex) {
                    handleError('Error in worker importScripts: ' + ex.message);
                }
            }

            for (i = 0, len = scripts.length; i < len; i++) {
                fetchScript(scripts[i], handleRequest);
            }
        };

        /**
         * Builds the wrapper object that will be returned by this constructor.
         * This is done so that the worker source can define the onmessage and
         * postMessage functions without overwriting the functions of the same
         * name defined on the worker instances.
         */
        function buildWrapper() {
            return {
                constructor: Worker,
                lazy: true,
                terminate: function() {},
                onmessage: function() {},
                onerror: function() {},
                postMessage: function(msg) {
                    if (typeof self.onmessage === 'function') {
                        try {
                            self.onmessage({data: msg});
                        } catch (ex) {
                            handleError('Error in worker onmessage: ' + ex.message);
                        }
                    }
                }
            };
        }

        // Build the wrapper, load the worker source, and return.
        wrapper = buildWrapper();
        init();
        return wrapper;
    }
    Worker.lazy = true;

    /**
     * Exports lazy-worker into the global scope.
     * This will replace the native Worker implementation if it exists.
     */
    function exportWorker() {
        globals.Worker = Worker;
    }

    if (!globals.Worker) {
        exportWorker();
    }

    globals.lazyWorker = {
        exportWorker: exportWorker,
        Worker: Worker
    };

}(window));
<commit_msg>cleanup<commit_after>/*jslint evil: true*/ // Get off my back, Mr. Crockford. Lazy workers need eval.
(function (globals) {

    /**
     * Constructor for new lazy-workers.
     * @param scriptUrl url of the script containing the worker source
     */
    function Worker(scriptUrl) {
        var self = this,
            wrapper;

        self.lazy = true;

        /**
         * Constructor for ErrorEvent objects.
         * @param type a string representing the error type
         */
        function ErrorEvent(type) {
            this.type = type;
            this.bubbles = false;
            this.cancelBubble = false;
            this.cancelable = false;
            this.defaultPrevented = false;
            this.eventPhase = 0;
            this.filename = '';
            this.lineno = 0;
            this.returnValue = true;
            this.timestamp = (new Date()).getTime();
        }

        /**
         * Generates an ErrorEvent with the given message and calls the worker
         * wrapper's onerror function.
         * @param errorMessage
         */
        function handleError(errorMessage) {
            var errorEvent;

            if (typeof wrapper.onerror === 'function') {
                errorEvent = new ErrorEvent('error');
                errorEvent.message = errorMessage;
                wrapper.onerror(errorEvent);
            }
        }

        /**
         * Initializes the worker by eval-ing the worker's source.
         */
        function init() {
            fetchScript(scriptUrl, function(request) {
                evalWorkerScript(request.responseText);
            });
        }

        /**
         * Fetches the worker source synchronously for the given url and calls
         * the provided function with the resulting XMLHttpRequest object.
         * @param url
         * @param f
         */
        function fetchScript(url, f) {
            var request = new XMLHttpRequest();

            request.open('GET', url, false);
            request.onload = function() {
                f(request);
            };
            request.send();
        }

        /**
         * Evals the given worker source string and sets up any worker-global
         * variables needed.
         * @param src
         */
        function evalWorkerScript(src) {
            // Make importScripts available to eval'ed code
            var importScripts = self.importScripts;

            try {
                eval(src);
            } catch (ex) {
                handleError('Error in worker script: ' + ex.message);
            }
        }

        /**
         * Posts a message to the main thread (the worker instance's onmessage
         * function in the case of lazy-worker).
         * @param msg
         */
        self.postMessage = function(msg) {
            if (typeof wrapper.onmessage === 'function') {
                try {
                    wrapper.onmessage({data: msg});
                } catch (ex) {
                    handleError('Error on onmessage handler: ' + ex.message);
                }
            }
        };

        /**
         * Implementation of the web worker importScripts function that loads
         * scripts synchronously and evals them.
         * @param script one or more string urls
         */
        self.importScripts = function(script) {
            var scripts = arguments.length > 1 ? arguments : [script],
                i,
                len;

            function handleRequest(request) {
                var scriptSource = request.responseText;

                try {
                    eval(scriptSource);
                } catch (ex) {
                    handleError('Error in worker importScripts: ' + ex.message);
                }
            }

            for (i = 0, len = scripts.length; i < len; i++) {
                fetchScript(scripts[i], handleRequest);
            }
        };

        /**
         * Builds the wrapper object that will be returned by this constructor.
         * This is done so that the worker source can define the onmessage and
         * postMessage functions without overwriting the functions of the same
         * name defined on the worker instances.
         */
        function buildWrapper() {
            return {
                constructor: Worker,
                lazy: true,
                terminate: function() {},
                onmessage: function() {},
                onerror: function() {},
                postMessage: function(msg) {
                    if (typeof self.onmessage === 'function') {
                        try {
                            self.onmessage({data: msg});
                        } catch (ex) {
                            handleError('Error in worker onmessage: ' + ex.message);
                        }
                    }
                }
            };
        }

        // Build the wrapper, load the worker source, and return.
        wrapper = buildWrapper();
        init();
        return wrapper;
    }
    Worker.lazy = true;

    /**
     * Exports lazy-worker into the global scope.
     * This will replace the native Worker implementation if it exists.
     */
    function exportWorker() {
        globals.Worker = Worker;
    }

    if (!globals.Worker) {
        exportWorker();
    }

    globals.lazyWorker = {
        exportWorker: exportWorker,
        Worker: Worker
    };

}(window));
<|endoftext|>


<commit_before>'use strict';
var phraseValidator = require('./validators/phrase.validator');
var CodeCompiler = require('./compilers/code.compiler');
var regexpGenerator = require('./regexpGenerator');
var paramsExtractor = require('./paramsExtractor');
var ComposrError = require('./ComposrError');
var mockedExpress = require('./mock');
var utils = require('./utils');

var q = require('q');
var _ = require('lodash');
var XRegExp = require('xregexp').XRegExp;

var DEFAULT_PHRASE_PARAMETERS = ['req', 'res', 'next', 'corbelDriver', 'domain', 'require'];

var PhraseManager = function(options) {
  this.events = options.events;
  this.requirer = options.requirer;
};

PhraseManager.prototype = new CodeCompiler({
  itemName: 'phrase',
  item: '__phrases',
  validator: phraseValidator
});

PhraseManager.prototype.__preCompile = function(domain, phrase) {
  var phraseId = this._generateId(phrase.url, domain);

  if (!phrase.id) {
    phrase.id = phraseId;
  }
};

PhraseManager.prototype.__preAdd = function(domain, compiled) {
  var phrasesWithTheSamePath = this._filterByRegexp(domain, compiled.regexpReference.regexp);

  if (phrasesWithTheSamePath.length > 0) {
    this.events.emit('warn', 'phrase:path:duplicated', compiled.id);
  }
};

//Modifies the internal __phrases list
PhraseManager.prototype._addToList = function(domain, phraseCompiled) {
  if (!domain || !phraseCompiled) {
    return false;
  }

  if (typeof(phraseCompiled) !== 'object' || phraseCompiled.hasOwnProperty('id') === false || !phraseCompiled.id) {
    return false;
  }

  if (!this.__phrases[domain]) {
    this.__phrases[domain] = [];
  }

  var index = this._getPhraseIndexById(domain, phraseCompiled.id);

  if (index === -1) {
    this.__phrases[domain].push(phraseCompiled);
  } else {
    this.__phrases[domain][index] = phraseCompiled;
  }

  return true;
};

//Removes phrases from memory
PhraseManager.prototype._unregister = function(domain, id) {
  var index = this._getPhraseIndexById(domain, id);
  if (index !== -1) {
    this.__phrases[domain].splice(index, 1);
  }
};

PhraseManager.prototype._compile = function(phrase) {
  try {
    var module = this;

    var compiled = {};

    compiled.url = phrase.url;
    compiled.id = phrase.id;
    //The regexp reference dictaminates the routing mechanisms
    compiled.regexpReference = regexpGenerator.regexpReference(phrase.url);

    var methods = ['get', 'put', 'post', 'delete', 'options'];

    compiled.codes = {};

    //Create in memory functions with the evaluation of the codes
    methods.forEach(function(method) {
      if (phrase[method] && (phrase[method].code || phrase[method].codehash)) {
        var phraseIdentifier = phrase.id ? phrase.id : phrase.url;
        module.events.emit('debug', 'phrase_manager:evaluatecode:', method, phraseIdentifier);

        var code;

        if (phrase[method].codehash) {
          code = utils.decodeFromBase64(phrase[method].codehash);
        } else {
          code = phrase[method].code;
        }

        compiled.codes[method] = module._evaluateCode(code, DEFAULT_PHRASE_PARAMETERS);
      }
    });

    module.events.emit('debug', 'phrase:compiled', compiled);

    return compiled;

  } catch (e) {
    //Somehow it has tried to compile an invalid phrase. Notify it and return false.
    //Catching errors and returning false here is important for not having an unstable phrases stack.
    this.events.emit('error', 'phrase:not:usable', phrase.url, e);
    return false;
  }

};

//Executes a phrase by id
PhraseManager.prototype.runById = function(domain, id, verb, params) {
  if (utils.values.isFalsy(verb)) {
    verb = 'get';
  }

  var phrase = this.getById(domain, id);

  if (this.canBeRun(phrase, verb)) {
    return this._run(phrase, verb, params, domain);
  } else {
    return q.reject('phrase:cant:be:runned');
  }

};

//Executes a phrase matching a path
PhraseManager.prototype.runByPath = function(domain, path, verb, params) {
  if (utils.values.isFalsy(verb)) {
    verb = 'get';
  }

  var phrase = this.getByMatchingPath(domain, path, verb);

  if (this.canBeRun(phrase, verb)) {
    if (!params) {
      params = {};
    }

    if (!params.reqParams) {
      //extract params from path
      params.reqParams = paramsExtractor.extract(path, phrase.regexpReference);
    }

    return this._run(phrase, verb, params, domain);
  } else {
    return q.reject('phrase:cant:be:runned');
  }

};

//Checks if it can be run
PhraseManager.prototype.canBeRun = function(phrase, verb) {
  if (phrase && phrase.codes[verb] && phrase.codes[verb].error === false) {
    return true;
  } else {
    return false;
  }
};

//Executes a phrase
PhraseManager.prototype._run = function(phrase, verb, params, domain) {
  this.events.emit('debug', 'running:phrase:' + phrase.id + ':' + verb);

  //var phraseCode = phrase.codes[verb].fn;
  var phraseScript = phrase.codes[verb].script;
  var callerParameters = {};

  var resWrapper = mockedExpress.res();
  var nextWrapper = mockedExpress.next();

  if (!params) {
    params = {};
  }

  if (!params.req) {
    var reqParams = {};

    if (params.reqHeaders) {
      reqParams.headers = params.reqHeaders;
    }

    if (params.reqBody) {
      reqParams.body = params.reqBody;
    }

    if (params.reqParams) {
      reqParams.params = params.reqParams;
    }

    callerParameters.req = mockedExpress.req(reqParams);
  } else {
    callerParameters.req = params.req;
  }

  if (params.res) {
    var previousRes = params.res;
    resWrapper.promise.then(function(response) {
      return previousRes.status(response.status)[resWrapper._action](response.body);
    }).catch(function(errResponse) {
      return previousRes.status(errResponse.status)[resWrapper._action](errResponse.body);
    });
  }

  callerParameters.res = resWrapper;

  if (params.next) {
    var previousNext = params.next;
    nextWrapper.promise.then(function() {
      previousNext();
    });
  }

  callerParameters.next = nextWrapper.resolve;

  if (!params.corbelDriver) {
    callerParameters.corbelDriver = null;
  } else {
    callerParameters.corbelDriver = params.corbelDriver;
  }

  callerParameters.domain = domain;

  callerParameters.require = this.requirer.forDomain(domain);

  //trigger the execution
  //phraseCode.apply(null, _.values(callerParameters));
  try {
    phraseScript.runInNewContext(callerParameters, {
      timeout: params.timeout || 10000
    });

  } catch (e) {
    //vm throws an error when timedout
    this.events.emit('warn', 'phrase:timedout', phrase.url, e);
    resWrapper.status(503).send(new ComposrError('error:phrase:timedout:' + phrase.url, 'The phrase endpoint is timing out', 503));
  }

  //Resolve on any promise resolution or rejection, either res or next
  return Promise.race([resWrapper.promise, nextWrapper.promise]);
};

//Returns a list of elements matching the same regexp
PhraseManager.prototype._filterByRegexp = function(domain, regexp) {
  var candidates = this._getPhrasesAsList(domain);

  return _.filter(candidates, function(candidate) {
    return candidate.regexpReference.regexp === regexp;
  });
};

//Flattens all the phrases in a single list array
PhraseManager.prototype._getPhrasesAsList = function(domain) {
  var list = [];
  var module = this;

  if (utils.values.isFalsy(domain)) {
    list = _.flatten(Object.keys(this.__phrases).map(function(key) {
      return module.__phrases[key];
    }));
  } else if (this.__phrases[domain]) {
    list = this.__phrases[domain];
  }

  return list;
};

//Returns one phrase matching the id
PhraseManager.prototype.getById = function(domain, id) {
  var candidates = this._getPhrasesAsList(domain);
  var index = this._getPhraseIndexById(domain, id);

  return index !== -1 ? candidates[index] : null;
};

//Returns the index of a phrase that matches by id
PhraseManager.prototype._getPhraseIndexById = function(domain, id) {
  var candidates = this._getPhrasesAsList(domain);
  var index = -1;

  for (var i = 0; i < candidates.length; i++) {
    if (candidates[i].id === id) {
      index = i;
      break;
    }
  }

  return index;
};

//Get all the phrases, or all the phrases for one domain
PhraseManager.prototype.getPhrases = function(domain) {
  if (!domain) {
    return this._getPhrasesAsList();
  }

  return this.__phrases[domain] ? this.__phrases[domain] : null;
};

/**
      CORE Entry point. One of the purposes of composr-core is to provide a fast and reliable
      getByMatchingPath method.
     **/
PhraseManager.prototype.getByMatchingPath = function(domain, path, verb) {
  var candidate = null;

  if (!verb) {
    verb = 'get';
  }

  domain = utils.values.isFalsy(domain) ? null : domain;

  this.events.emit('debug', 'phrase:getByMatchingPath:' + domain + ':' + path + ':' + verb);

  if (utils.values.isFalsy(path)) {
    this.events.emit('error', 'phrase:getByMatchingPath:path:undefined');
    return candidate;
  }

  var queryParamsString = path.indexOf('?') !== -1 ? path.substring(path.indexOf('?'), path.length) : '';

  path = path.replace(queryParamsString, '');

  if (domain === null) {
    this.events.emit('warn', 'phrase:getByMatchingPath:noDomain:matchingAgainstAll:expensiveMethod');
  }

  var candidates = this._getPhrasesAsList(domain);

  this.events.emit('debug', 'evaluating:' + candidates.length + ':candidates');

  candidates = _.compact(candidates.map(function(phrase) {
    if (phrase.codes[verb] &&
      XRegExp.test(path, phrase.regexpReference.xregexp)) {
      return phrase;
    }
  }));

  this.events.emit('debug', 'found:' + candidates.length + ':candidates');

  if (candidates.length === 0) {
    this.events.emit('debug', 'notfound:candidates:path:' + path + ':' + verb);
    return candidate;
  } else {
    candidate = candidates[0];
    this.events.emit('debug', 'using:candidate:' + candidate.id + ':' + verb);
    return candidate;
  }

};

//Counts all the loaded phrases
PhraseManager.prototype.count = function() {
  return this._getPhrasesAsList().length;
};

//Generates a PhraseID from a url an a domain
PhraseManager.prototype._generateId = function(url, domain) {
  return domain + '!' + url.replace(/\//g, '!');
};

module.exports = PhraseManager;<commit_msg>fixes the req passing parameters from composr<commit_after>'use strict';
var phraseValidator = require('./validators/phrase.validator');
var CodeCompiler = require('./compilers/code.compiler');
var regexpGenerator = require('./regexpGenerator');
var paramsExtractor = require('./paramsExtractor');
var ComposrError = require('./ComposrError');
var mockedExpress = require('./mock');
var utils = require('./utils');

var q = require('q');
var _ = require('lodash');
var XRegExp = require('xregexp').XRegExp;

var DEFAULT_PHRASE_PARAMETERS = ['req', 'res', 'next', 'corbelDriver', 'domain', 'require'];

var PhraseManager = function(options) {
  this.events = options.events;
  this.requirer = options.requirer;
};

PhraseManager.prototype = new CodeCompiler({
  itemName: 'phrase',
  item: '__phrases',
  validator: phraseValidator
});

PhraseManager.prototype.__preCompile = function(domain, phrase) {
  var phraseId = this._generateId(phrase.url, domain);

  if (!phrase.id) {
    phrase.id = phraseId;
  }
};

PhraseManager.prototype.__preAdd = function(domain, compiled) {
  var phrasesWithTheSamePath = this._filterByRegexp(domain, compiled.regexpReference.regexp);

  if (phrasesWithTheSamePath.length > 0) {
    this.events.emit('warn', 'phrase:path:duplicated', compiled.id);
  }
};

//Modifies the internal __phrases list
PhraseManager.prototype._addToList = function(domain, phraseCompiled) {
  if (!domain || !phraseCompiled) {
    return false;
  }

  if (typeof(phraseCompiled) !== 'object' || phraseCompiled.hasOwnProperty('id') === false || !phraseCompiled.id) {
    return false;
  }

  if (!this.__phrases[domain]) {
    this.__phrases[domain] = [];
  }

  var index = this._getPhraseIndexById(domain, phraseCompiled.id);

  if (index === -1) {
    this.__phrases[domain].push(phraseCompiled);
  } else {
    this.__phrases[domain][index] = phraseCompiled;
  }

  return true;
};

//Removes phrases from memory
PhraseManager.prototype._unregister = function(domain, id) {
  var index = this._getPhraseIndexById(domain, id);
  if (index !== -1) {
    this.__phrases[domain].splice(index, 1);
  }
};

PhraseManager.prototype._compile = function(phrase) {
  try {
    var module = this;

    var compiled = {};

    compiled.url = phrase.url;
    compiled.id = phrase.id;
    //The regexp reference dictaminates the routing mechanisms
    compiled.regexpReference = regexpGenerator.regexpReference(phrase.url);

    var methods = ['get', 'put', 'post', 'delete', 'options'];

    compiled.codes = {};

    //Create in memory functions with the evaluation of the codes
    methods.forEach(function(method) {
      if (phrase[method] && (phrase[method].code || phrase[method].codehash)) {
        var phraseIdentifier = phrase.id ? phrase.id : phrase.url;
        module.events.emit('debug', 'phrase_manager:evaluatecode:', method, phraseIdentifier);

        var code;

        if (phrase[method].codehash) {
          code = utils.decodeFromBase64(phrase[method].codehash);
        } else {
          code = phrase[method].code;
        }

        compiled.codes[method] = module._evaluateCode(code, DEFAULT_PHRASE_PARAMETERS);
      }
    });

    module.events.emit('debug', 'phrase:compiled', compiled);

    return compiled;

  } catch (e) {
    //Somehow it has tried to compile an invalid phrase. Notify it and return false.
    //Catching errors and returning false here is important for not having an unstable phrases stack.
    this.events.emit('error', 'phrase:not:usable', phrase.url, e);
    return false;
  }

};

//Executes a phrase by id
PhraseManager.prototype.runById = function(domain, id, verb, params) {
  if (utils.values.isFalsy(verb)) {
    verb = 'get';
  }

  var phrase = this.getById(domain, id);

  if (this.canBeRun(phrase, verb)) {
    return this._run(phrase, verb, params, domain);
  } else {
    return q.reject('phrase:cant:be:runned');
  }

};

//Executes a phrase matching a path
PhraseManager.prototype.runByPath = function(domain, path, verb, params) {
  if (utils.values.isFalsy(verb)) {
    verb = 'get';
  }

  var phrase = this.getByMatchingPath(domain, path, verb);

  if (this.canBeRun(phrase, verb)) {
    if (!params) {
      params = {};
    }

    if (!params.reqParams) {
      //extract params from path
      params.reqParams = paramsExtractor.extract(path, phrase.regexpReference);
    }

    return this._run(phrase, verb, params, domain);
  } else {
    return q.reject('phrase:cant:be:runned');
  }

};

//Checks if it can be run
PhraseManager.prototype.canBeRun = function(phrase, verb) {
  if (phrase && phrase.codes[verb] && phrase.codes[verb].error === false) {
    return true;
  } else {
    return false;
  }
};

//Executes a phrase
PhraseManager.prototype._run = function(phrase, verb, params, domain) {
  this.events.emit('debug', 'running:phrase:' + phrase.id + ':' + verb);

  //var phraseCode = phrase.codes[verb].fn;
  var phraseScript = phrase.codes[verb].script;
  var callerParameters = {};

  var resWrapper = mockedExpress.res();
  var nextWrapper = mockedExpress.next();

  if (!params) {
    params = {};
  }

  if (!params.req) {
    var reqParams = {};

    if (params.reqHeaders) {
      reqParams.headers = params.reqHeaders;
    }

    if (params.reqBody) {
      reqParams.body = params.reqBody;
    }

    if (params.reqParams) {
      reqParams.params = params.reqParams;
    }

    callerParameters.req = mockedExpress.req(reqParams);
  } else {
    //Overwrite params extraction
    if (params.reqParams) {
      params.req.params = params.reqParams;
    }

    callerParameters.req = params.req;
  }

  if (params.res) {
    var previousRes = params.res;
    resWrapper.promise.then(function(response) {
      return previousRes.status(response.status)[resWrapper._action](response.body);
    }).catch(function(errResponse) {
      return previousRes.status(errResponse.status)[resWrapper._action](errResponse.body);
    });
  }

  callerParameters.res = resWrapper;

  if (params.next) {
    var previousNext = params.next;
    nextWrapper.promise.then(function() {
      previousNext();
    });
  }

  callerParameters.next = nextWrapper.resolve;

  if (!params.corbelDriver) {
    callerParameters.corbelDriver = null;
  } else {
    callerParameters.corbelDriver = params.corbelDriver;
  }

  callerParameters.domain = domain;

  callerParameters.require = this.requirer.forDomain(domain);

  //trigger the execution
  //phraseCode.apply(null, _.values(callerParameters));
  try {
    phraseScript.runInNewContext(callerParameters, {
      timeout: params.timeout || 10000
    });

  } catch (e) {
    //vm throws an error when timedout
    this.events.emit('warn', 'phrase:timedout', phrase.url, e);
    resWrapper.status(503).send(new ComposrError('error:phrase:timedout:' + phrase.url, 'The phrase endpoint is timing out', 503));
  }

  //Resolve on any promise resolution or rejection, either res or next
  return Promise.race([resWrapper.promise, nextWrapper.promise]);
};

//Returns a list of elements matching the same regexp
PhraseManager.prototype._filterByRegexp = function(domain, regexp) {
  var candidates = this._getPhrasesAsList(domain);

  return _.filter(candidates, function(candidate) {
    return candidate.regexpReference.regexp === regexp;
  });
};

//Flattens all the phrases in a single list array
PhraseManager.prototype._getPhrasesAsList = function(domain) {
  var list = [];
  var module = this;

  if (utils.values.isFalsy(domain)) {
    list = _.flatten(Object.keys(this.__phrases).map(function(key) {
      return module.__phrases[key];
    }));
  } else if (this.__phrases[domain]) {
    list = this.__phrases[domain];
  }

  return list;
};

//Returns one phrase matching the id
PhraseManager.prototype.getById = function(domain, id) {
  var candidates = this._getPhrasesAsList(domain);
  var index = this._getPhraseIndexById(domain, id);

  return index !== -1 ? candidates[index] : null;
};

//Returns the index of a phrase that matches by id
PhraseManager.prototype._getPhraseIndexById = function(domain, id) {
  var candidates = this._getPhrasesAsList(domain);
  var index = -1;

  for (var i = 0; i < candidates.length; i++) {
    if (candidates[i].id === id) {
      index = i;
      break;
    }
  }

  return index;
};

//Get all the phrases, or all the phrases for one domain
PhraseManager.prototype.getPhrases = function(domain) {
  if (!domain) {
    return this._getPhrasesAsList();
  }

  return this.__phrases[domain] ? this.__phrases[domain] : null;
};

/**
      CORE Entry point. One of the purposes of composr-core is to provide a fast and reliable
      getByMatchingPath method.
     **/
PhraseManager.prototype.getByMatchingPath = function(domain, path, verb) {
  var candidate = null;

  if (!verb) {
    verb = 'get';
  }

  domain = utils.values.isFalsy(domain) ? null : domain;

  this.events.emit('debug', 'phrase:getByMatchingPath:' + domain + ':' + path + ':' + verb);

  if (utils.values.isFalsy(path)) {
    this.events.emit('error', 'phrase:getByMatchingPath:path:undefined');
    return candidate;
  }

  var queryParamsString = path.indexOf('?') !== -1 ? path.substring(path.indexOf('?'), path.length) : '';

  path = path.replace(queryParamsString, '');

  if (domain === null) {
    this.events.emit('warn', 'phrase:getByMatchingPath:noDomain:matchingAgainstAll:expensiveMethod');
  }

  var candidates = this._getPhrasesAsList(domain);

  this.events.emit('debug', 'evaluating:' + candidates.length + ':candidates');

  candidates = _.compact(candidates.map(function(phrase) {
    if (phrase.codes[verb] &&
      XRegExp.test(path, phrase.regexpReference.xregexp)) {
      return phrase;
    }
  }));

  this.events.emit('debug', 'found:' + candidates.length + ':candidates');

  if (candidates.length === 0) {
    this.events.emit('debug', 'notfound:candidates:path:' + path + ':' + verb);
    return candidate;
  } else {
    candidate = candidates[0];
    this.events.emit('debug', 'using:candidate:' + candidate.id + ':' + verb);
    return candidate;
  }

};

//Counts all the loaded phrases
PhraseManager.prototype.count = function() {
  return this._getPhrasesAsList().length;
};

//Generates a PhraseID from a url an a domain
PhraseManager.prototype._generateId = function(url, domain) {
  return domain + '!' + url.replace(/\//g, '!');
};

module.exports = PhraseManager;<|endoftext|>


<commit_before>/**
 * Push out alerts
 */

var gcm = require('node-gcm');
var db = require('./mongodb.js');
var deviceDao = require('./deviceDao');

var sendNotification = function(devices, message)
{
	var registrationIds = [];
	var sender = new gcm.Sender(process.env.GOOGLE_API_KEY);

	// TODO: can only send a max of 1000 registration IDs in a single msg.
	// This means that if the app does actually take off, this will have
	// to be a bit more clever. Unless the node-gcm lib already handles this -
	// must check.
	for (var i = 0; i < devices.length; i++)
	{
		registrationIds.push(devices[i]._id);
	}

	sender.send(message, registrationIds, 4, function (err, result) {
		if (err || !result)
		{
			console.log("Error pushing GCM message: " + err);
			return;
		}

		console.log(JSON.stringify(result, null, 2));

		// Now, we have to process the GCM response - there are two things we have to
		// look for
		// 1. A particular registration ID is invalid - likely because the user uninstalled, for
		//    example. We should delete the device.
		// 2. A registration ID has changed - not sure how this can happen, but if it does, we
		//    need to change our ID too.

		if (result.failure > 0 || result.canonical_ids > 0)
		{
			console.log("At least one error or change detected.. processing response...");

			// At least one ID failed or changed - we have to loop through in order.
			// Luckily the order is supposed to be the same as our registrationIds array
			for (var i = 0; i < result.results.length; i++)
			{
				var res = result.results[i];
				var regid = registrationIds[i];

				// There's a whole ton of possible error conditions but these look like the
				// main ones to worry about in terms of removing devices:
				if (res.error &&
						(res.error == "NotRegistered" || res.error == "InvalidRegistration"))
				{
					// device probably uninstalled the app, so remove it from the DB
					console.log("detected invalid registration, removing device: " + regid);
					deviceDao.deleteDevice(regid).then();
				}
				else if (res.message_id && res.canonical_id)
				{
					// need to switch the existing device's ID in the database:
					var newId = res.canonical_id;
					console.log("Canonical device ID change from " + regid + " to " + newId);
					deviceDao.changeDeviceId(regid, newId).then();
				}
			}
		}
	});
};

exports.triggerAlert = function(item)
{
	console.log("ALERT: " + item.title + ": " + item.description);

	// Build the message:
	var message = new gcm.Message({
		collapseKey: item.title,
		delayWhileIdle: true,
		timeToLive: 60 * 60 * 24, // 1 day
		data: {
			guid: item.guid,
			title: item.title,
			description: item.description,
			link: item.link
        }
	});

	var country = item.title;
	if (/^TEST: /.test(country))
	{
		country = country.substring(6);
		console.log("Test message detected, using country code '" + country + "'.");
	}

	// We're looking for devices that either have not specified any countries, or have
	// got the country listed in their preferences:
	db.devices.find(
			{ $or : [{ 'countries' : {$exists: false}}, { 'countries' : country }]},
			function(err, devices) {
		if (!err)
		{
			sendNotification(devices, message);
		}
		else
		{
			// log error, not much else we can do
			console.log("An error occurred fetching devices: " + err);
		}
	});

};

<commit_msg>Covert sendNotification to use promises<commit_after>/**
 * Push out alerts via Google Cloud Messaging
 */

var gcm = require('node-gcm');
var RSVP = require('rsvp');

var db = require('./mongodb.js');
var deviceDao = require('./deviceDao');

var sendNotification = function(devices, message)
{
	return new RSVP.Promise(function(resolve, reject) {

		var registrationIds = [];
		var sender = new gcm.Sender(process.env.GOOGLE_API_KEY);

		// TODO: can only send a max of 1000 registration IDs in a single msg.
		// This means that if the app does actually take off, this will have
		// to be a bit more clever. The node-gcm lib doesn't handle this.
		for (var i = 0; i < devices.length; i++)
		{
			registrationIds.push(devices[i]._id);
		}

		sender.send(message, registrationIds, 4, function (err, result) {
			if (err || !result)
			{
				console.log("Error pushing GCM message: " + err);
				reject(err);
			}

			//console.log(JSON.stringify(result, null, 2));
			console.log("GCM response: ", {
					multicast_id: result.multicast_id,
					success: result.success,
					failure: result.failure,
					canonical_ids: result.canonical_ids,
					result_size: result.results.length
				});

			if (result.failure > 0 || result.canonical_ids > 0)
			{
				console.log("At least one error or change detected.. processing response...");
				handleGcmErrorResponse(result, registrationIds, resolve, reject);
			}
			else
			{
				resolve();
			}
		});
	});
};

/**
 * Now, we have to process the GCM response - there are two things we have to
 * look for
 * 1. A particular registration ID is invalid - likely because the user uninstalled, for
 *    example. We should delete the device.
 * 2. A registration ID has changed - not sure how this can happen, but if it does, we
 *    need to change our ID too.
 */
function handleGcmErrorResponse(result, registrationIds, resolve, reject)
{
	// At least one ID failed or changed - we have to loop through in order.
	// Luckily the order is supposed to be the same as our registrationIds array
	var promises = [];
	for (var i = 0; i < result.results.length; i++)
	{
		var res = result.results[i];
		var regid = registrationIds[i];

		// There's a whole ton of possible error conditions but these look like the
		// main ones to worry about in terms of removing devices:
		if (res.error &&
				(res.error == "NotRegistered" || res.error == "InvalidRegistration"))
		{
			// device probably uninstalled the app, so remove it from the DB
			console.log("detected invalid registration, removing device: " + regid);
			promises.push(deviceDao.deleteDevice(regid));
		}
		else if (res.message_id && res.canonical_id)
		{
			// need to switch the existing device's ID in the database:
			var newId = res.canonical_id;
			console.log("Canonical device ID change from " + regid + " to " + newId);
			promises.push(deviceDao.changeDeviceId(regid, newId));
		}
		else {
			// nothing to do here..
		}
	}

	// we actually don't want to reject if this fails, because it might stop alert processing
	// and it's actually not the key part of the job here. So, we need to log failures but
	// carry on and mark this as resolved.
	RSVP.allSettled(promises).then(resolve).catch(function(err) {
		// this should never happen
		console.log("GCM response processing failed: ", err);
		resolve();
	});
}

exports.triggerAlert = function(item)
{
	console.log("ALERT: " + item.title + ": " + item.description);

	// Build the message:
	var message = new gcm.Message({
		collapseKey: item.title,
		delayWhileIdle: true,
		timeToLive: 60 * 60 * 24, // 1 day
		data: {
			guid: item.guid,
			title: item.title,
			description: item.description,
			link: item.link
        }
	});

	var country = item.title;
	if (/^TEST: /.test(country))
	{
		country = country.substring(6);
		console.log("Test message detected, using country code '" + country + "'.");
	}

	// We're looking for devices that either have not specified any countries, or have
	// got the country listed in their preferences:
	db.devices.find(
			{ $or : [{ 'countries' : {$exists: false}}, { 'countries' : country }]},
			function(err, devices) {
		if (!err)
		{
			sendNotification(devices, message).then();
		}
		else
		{
			// log error, not much else we can do
			console.log("An error occurred fetching devices: " + err);
		}
	});

};

<|endoftext|>


<commit_before>import uuid from 'uuid';

/**
 * @class Channel
 * @memberof module:ancient-channels
 */
export default class Channel {
    /**
     * @constructs Channel
     * @param {Function} onConnected - Callback after connection
     * @param {Function} onDisconnected - Callback after disconnection
     * @param {Function} gotPackage - Callback when receiving a packet
     * @param {Function} sendPackage - Callback package sending
     */
    constructor(onConnected, onDisconnected, gotPackage, sendPackage) {
        /**
         * @type {Function}
         * @description Callback after connection.
         */
        this.onConnected = onConnected;

        /**
         * @type {Function}
         * @description Callback after disconnection.
         */
        this.onDisconnected = onDisconnected;

        /**
         * @type {Function}
         * @description Callback when receiving a packet.
         */
        this.gotPackage = gotPackage;

        /**
         * @type {Function}
         * @description Callback package sending.
         */
        this.sendPackage = sendPackage;

        /**
         * @protected
         * @type {Object}
         * @description An instance of the serializer class.
         */
        this._serializer = this._createSerializer();

        /**
         * @protected
         * @type {String}
         * @description Channel identifier.
         */
        this.id = null;

        /**
         * @protected
         * @type {Boolean}
         * @description Connection status.
         */
        this.isConnected = false;
    }

    /**
     * @protected
     * @param {String} message - Message for the remote side
     * @description Send the connection package.
     */
    connect(message) {
        this.id = this._getIdentifier();
        var pkg = this._packData({ id: this.id, request: null, warning: message }, 'SYN');
        this.sendPackage(this, pkg);
    }

    /**
     * @protected
     * @param {Object} request - Data packet for authorization
     * @param {String} request.id - New channel id
     * @param {String} request.request - Public key of authorization
     * @param {String} request.warning - Information message
     * @description Change the status of the channel when connected.
     */
    connected(request) {
        request = request || {
            id: this._getIdentifier(),
            request: null,
            warning: 'Data is not received, random data is used.'
        };

        this.isConnected = true;
        this.id = request.id;

        if (this._isFunction(this.onConnected)) {
            this.onConnected(this, request.warning);
        }
    }

    /**
     * @protected
     * @param {String} message - Message for the remote side
     * @description Send a shutdown packet.
     */
    disconnect(message) {
        var pkg = this._packData({ id: this.id, request: null, warning: message }, 'RST');
        this.sendPackage(this, pkg);
    }

    /**
     * @protected
     * @param {String} message - Message from the remote side
     * @description Change the status of the channel when disconnected.
     */
    disconnected(message) {
        this.isConnected = false;
        if (this._isFunction(this.onDisconnected)) {
            this.onDisconnected(this, message);
        }
    }

    /**
     * @protected
     * @param {String} request - Submitted data
     * @description Process the submitted data.
     */
    got(request) {
        if (!this._isString(request)) {
            throw new TypeError('\'request\' is not a string');
        }

        var pkg = this._unpackData(request);

        if (pkg.type == 'RST') {
            this.disconnected(pkg.data.warning);
        }
        else if (pkg.type == 'SYN') {
            this.connected(pkg.data);
        }
        else if (pkg.type == 'ACK') {
            this.gotPackage(this, {
                id: pkg.data.id,
                request: pkg.data.data,
                warning: pkg.data.warning
            });
        }
    }

    /**
     * @protected
     * @param {*} data - Submitted data
     * @description Generates and sends the packet.
     */
    send(data) {
        var pkg = this._packData({
            id: this.id,
            warning: null,
            data
        }, 'ACK');
        this.sendPackage(this, pkg);
    }

    /**
     * @protected
     * @returns {Object} Serializer class
     * @description Creates an instance of the serializer class.
     */
    _createSerializer() {
        return JSON;
    }

    /**
     * @protected
     * @returns {String} Identifier
     * @description Generates an identifier.
     */
    _generationUUID() {
        return uuid.v4();
    }

    /**
     * @protected
     * @returns {String} Identifier
     * @description Create a new one or return an existing identifier.
     */
    _getIdentifier() {
        var identifier = this._isString(this.id) ? this.id : this._generationUUID();
        return identifier;
    }

    /**
     * @protected
     * @param {*} data - Transmitted data
     * @param {String} type - Type of package
     * @returns {String} Package for transfer
     * @description Compresses the data from the request.
     */
    _packData(data, type) {
        type = this._isString(type) ? type : 'ACK';
        return this._serialization({ type, data });
    }

    /**
     * @protected
     * @param {String} request - Submitted data
     * @returns {Object} Source package
     * @description Decompresses the data from the request.
     */
    _unpackData(request) {
        return this._deserialization(request);
    }

    /**
     * @protected
     * @param {*} data
     * @returns {String} Compressed string
     * @description Serializes the data.
     */
    _serialization(data) {
        return this._serializer.stringify(data);
    }

    /**
     * @protected
     * @param {String} data
     * @returns {*} Source data
     * @description Deserializes the data.
     */
    _deserialization(data) {
        return this._serializer.parse(data);
    }

    /**
     * @protected
     * @param {String} value
     * @returns {Boolean} Result of checking
     * @description Checks the type of the variable.
     */
    _isFunction(value) {
        return typeof value === 'function';
    }

    /**
     * @protected
     * @param {String} value
     * @returns {Boolean} Result of checking
     * @description Checks the type of the variable.
     */
    _isString(value) {
        return typeof value === 'string';
    }
}
<commit_msg>Code optimization<commit_after>import uuid from 'uuid';

/**
 * @class Channel
 * @memberof module:ancient-channels
 */
export default class Channel {
    /**
     * @constructs Channel
     * @param {Function} onConnected - Callback after connection
     * @param {Function} onDisconnected - Callback after disconnection
     * @param {Function} gotPackage - Callback when receiving a packet
     * @param {Function} sendPackage - Callback package sending
     */
    constructor(onConnected, onDisconnected, gotPackage, sendPackage) {
        /**
         * @type {Function}
         * @description Callback after connection.
         */
        this.onConnected = onConnected;

        /**
         * @type {Function}
         * @description Callback after disconnection.
         */
        this.onDisconnected = onDisconnected;

        /**
         * @type {Function}
         * @description Callback when receiving a packet.
         */
        this.gotPackage = gotPackage;

        /**
         * @type {Function}
         * @description Callback package sending.
         */
        this.sendPackage = sendPackage;

        /**
         * @protected
         * @type {Object}
         * @description An instance of the serializer class.
         */
        this._serializer = this._createSerializer();

        /**
         * @protected
         * @type {String}
         * @description Channel identifier.
         */
        this.id = null;

        /**
         * @protected
         * @type {Boolean}
         * @description Connection status.
         */
        this.isConnected = false;
    }

    /**
     * @protected
     * @param {String} message - Message for the remote side
     * @description Send the connection package.
     */
    connect(message) {
        this.id = this._getIdentifier();
        var pkg = this._packData({ id: this.id, request: null, warning: message }, 'SYN');
        this.sendPackage(this, pkg);
    }

    /**
     * @protected
     * @param {Object} request - Data packet for authorization
     * @param {String} request.id - New channel id
     * @param {String} request.request - Public key of authorization
     * @param {String} request.warning - Information message
     * @description Change the status of the channel when connected.
     */
    connected(request) {
        request = request || {
            id: this._getIdentifier(),
            warning: 'Data is not received, random data is used.',
            request: null
        };

        this.isConnected = true;
        this.id = request.id;

        if (this._isFunction(this.onConnected)) {
            this.onConnected(this, request.warning);
        }
    }

    /**
     * @protected
     * @param {String} message - Message for the remote side
     * @description Send a shutdown packet.
     */
    disconnect(message) {
        var pkg = this._packData({ id: this.id, request: null, warning: message }, 'RST');
        this.sendPackage(this, pkg);
    }

    /**
     * @protected
     * @param {String} message - Message from the remote side
     * @description Change the status of the channel when disconnected.
     */
    disconnected(message) {
        this.isConnected = false;
        if (this._isFunction(this.onDisconnected)) {
            this.onDisconnected(this, message);
        }
    }

    /**
     * @protected
     * @param {String} request - Submitted data
     * @description Process the submitted data.
     */
    got(request) {
        if (!this._isString(request)) {
            throw new TypeError('\'request\' is not a string');
        }

        var pkg = this._unpackData(request);

        if (pkg.type == 'RST') {
            this.disconnected(pkg.data.warning);
        }
        else if (pkg.type == 'SYN') {
            this.connected(pkg.data);
        }
        else if (pkg.type == 'ACK') {
            this.gotPackage(this, pkg.data);
        }
    }

    /**
     * @protected
     * @param {Object} data - Submitted data
     * @param {String} message - Message for the remote side
     * @description Generates and sends the packet.
     */
    send(data, message) {
        var pkg = this._packData({ id: this.id, request: data, warning: message }, 'ACK');
        this.sendPackage(this, pkg);
    }

    /**
     * @protected
     * @returns {Object} Serializer class
     * @description Creates an instance of the serializer class.
     */
    _createSerializer() {
        return JSON;
    }

    /**
     * @protected
     * @returns {String} Identifier
     * @description Generates an identifier.
     */
    _generationUUID() {
        return uuid.v4();
    }

    /**
     * @protected
     * @returns {String} Identifier
     * @description Create a new one or return an existing identifier.
     */
    _getIdentifier() {
        var identifier = this._isString(this.id) ? this.id : this._generationUUID();
        return identifier;
    }

    /**
     * @protected
     * @param {*} data - Transmitted data
     * @param {String} type - Type of package
     * @returns {String} Package for transfer
     * @description Compresses the data from the request.
     */
    _packData(data, type) {
        type = this._isString(type) ? type : 'ACK';
        return this._serialization({ type, data });
    }

    /**
     * @protected
     * @param {String} request - Submitted data
     * @returns {Object} Source package
     * @description Decompresses the data from the request.
     */
    _unpackData(request) {
        return this._deserialization(request);
    }

    /**
     * @protected
     * @param {*} data
     * @returns {String} Compressed string
     * @description Serializes the data.
     */
    _serialization(data) {
        return this._serializer.stringify(data);
    }

    /**
     * @protected
     * @param {String} data
     * @returns {*} Source data
     * @description Deserializes the data.
     */
    _deserialization(data) {
        return this._serializer.parse(data);
    }

    /**
     * @protected
     * @param {String} value
     * @returns {Boolean} Result of checking
     * @description Checks the type of the variable.
     */
    _isFunction(value) {
        return typeof value === 'function';
    }

    /**
     * @protected
     * @param {String} value
     * @returns {Boolean} Result of checking
     * @description Checks the type of the variable.
     */
    _isString(value) {
        return typeof value === 'string';
    }
}
<|endoftext|>