<commit_before>'use strict';

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

const Exchange = require ('./base/Exchange');

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

module.exports = class independentreserve extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'independentreserve',
            'name': 'Independent Reserve',
            'countries': [ 'AU', 'NZ' ], // Australia, New Zealand
            'rateLimit': 1000,
            'has': {
                'CORS': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/30521662-cf3f477c-9bcb-11e7-89bc-d1ac85012eda.jpg',
                'api': {
                    'public': 'https://api.independentreserve.com/Public',
                    'private': 'https://api.independentreserve.com/Private',
                },
                'www': 'https://www.independentreserve.com',
                'doc': 'https://www.independentreserve.com/API',
            },
            'api': {
                'public': {
                    'get': [
                        'GetValidPrimaryCurrencyCodes',
                        'GetValidSecondaryCurrencyCodes',
                        'GetValidLimitOrderTypes',
                        'GetValidMarketOrderTypes',
                        'GetValidOrderTypes',
                        'GetValidTransactionTypes',
                        'GetMarketSummary',
                        'GetOrderBook',
                        'GetTradeHistorySummary',
                        'GetRecentTrades',
                        'GetFxRates',
                    ],
                },
                'private': {
                    'post': [
                        'PlaceLimitOrder',
                        'PlaceMarketOrder',
                        'CancelOrder',
                        'GetOpenOrders',
                        'GetClosedOrders',
                        'GetClosedFilledOrders',
                        'GetOrderDetails',
                        'GetAccounts',
                        'GetTransactions',
                        'GetDigitalCurrencyDepositAddress',
                        'GetDigitalCurrencyDepositAddresses',
                        'SynchDigitalCurrencyDepositAddressWithBlockchain',
                        'WithdrawDigitalCurrency',
                        'RequestFiatWithdrawal',
                        'GetTrades',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.5 / 100,
                    'maker': 0.5 / 100,
                    'percentage': true,
                    'tierBased': false,
                },
            },
        });
    }

    async fetchMarkets () {
        let baseCurrencies = await this.publicGetGetValidPrimaryCurrencyCodes ();
        let quoteCurrencies = await this.publicGetGetValidSecondaryCurrencyCodes ();
        let result = [];
        for (let i = 0; i < baseCurrencies.length; i++) {
            let baseId = baseCurrencies[i];
            let baseIdUppercase = baseId.toUpperCase ();
            let base = this.commonCurrencyCode (baseIdUppercase);
            for (let j = 0; j < quoteCurrencies.length; j++) {
                let quoteId = quoteCurrencies[j];
                let quoteIdUppercase = quoteId.toUpperCase ();
                let quote = this.commonCurrencyCode (quoteIdUppercase);
                let id = baseId + '/' + quoteId;
                let symbol = base + '/' + quote;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'info': id,
                });
            }
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let balances = await this.privatePostGetAccounts ();
        let result = { 'info': balances };
        for (let i = 0; i < balances.length; i++) {
            let balance = balances[i];
            let currencyCode = balance['CurrencyCode'];
            let uppercase = currencyCode.toUpperCase ();
            let currency = this.commonCurrencyCode (uppercase);
            let account = this.account ();
            account['free'] = balance['AvailableBalance'];
            account['total'] = balance['TotalBalance'];
            account['used'] = account['total'] - account['free'];
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetOrderBook (this.extend ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
        }, params));
        let timestamp = this.parse8601 (response['CreatedTimestampUtc']);
        return this.parseOrderBook (response, timestamp, 'BuyOrders', 'SellOrders', 'Price', 'Volume');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.parse8601 (ticker['CreatedTimestampUtc']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let last = ticker['LastPrice'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': ticker['DayHighestPrice'],
            'low': ticker['DayLowestPrice'],
            'bid': ticker['CurrentHighestBidPrice'],
            'bidVolume': undefined,
            'ask': ticker['CurrentLowestOfferPrice'],
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': ticker['DayAvgPrice'],
            'baseVolume': ticker['DayVolumeXbtInSecondaryCurrrency'],
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetMarketSummary (this.extend ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
        }, params));
        return this.parseTicker (response, market);
    }

    parseOrder (order, market = undefined) {
        let symbol = undefined;
        if (typeof market === 'undefined') {
            symbol = market['symbol'];
        } else {
            market = this.findMarket (order['PrimaryCurrencyCode'] + '/' + order['SecondaryCurrencyCode']);
        }
        let orderType = this.safeValue (order, 'Type');
        if (orderType.indexOf ('Market') >= 0)
            orderType = 'market';
        else if (orderType.indexOf ('Limit') >= 0)
            orderType = 'limit';
        let side = undefined;
        if (orderType.indexOf ('Bid') >= 0)
            side = 'buy';
        else if (orderType.indexOf ('Offer') >= 0)
            side = 'sell';
        let timestamp = this.parse8601 (order['CreatedTimestampUtc']);
        let amount = this.safeFloat (order, 'VolumeOrdered');
        if (typeof amount === 'undefined')
            amount = this.safeFloat (order, 'Volume');
        let filled = this.safeFloat (order, 'VolumeFilled');
        let remaining = undefined;
        let feeRate = this.safeFloat (order, 'FeePercent');
        let feeCost = undefined;
        if (typeof amount !== 'undefined') {
            if (typeof filled !== 'undefined') {
                remaining = amount - filled;
                if (typeof feeRate !== 'undefined')
                    feeCost = feeRate * filled;
            }
        }
        let feeCurrency = undefined;
        if (typeof market !== 'undefined') {
            symbol = market['symbol'];
            feeCurrency = market['base'];
        }
        let fee = {
            'rate': feeRate,
            'cost': feeCost,
            'currency': feeCurrency,
        };
        let id = order['OrderGuid'];
        let status = this.parseOrderStatus (order['Status']);
        let cost = this.safeFloat (order, 'Value');
        let average = this.safeFloat (order, 'AvgPrice');
        let price = this.safeFloat (order, 'Price', average);
        return {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'lastTradeTimestamp': undefined,
            'symbol': symbol,
            'type': orderType,
            'side': side,
            'price': price,
            'cost': cost,
            'average': average,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
        };
    }

    parseOrderStatus (status) {
        let statuses = {
            'Open': 'open',
            'PartiallyFilled': 'open',
            'Filled': 'closed',
            'PartiallyFilledAndCancelled': 'canceled',
            'Cancelled': 'canceled',
            'PartiallyFilledAndExpired': 'canceled',
            'Expired': 'canceled',
        };
        if (status in statuses)
            return statuses[status];
        return status;
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const response = await this.privatePostGetOrderDetails (this.extend ({
            'orderGuid': id,
        }, params));
        let market = undefined;
        if (typeof symbol !== 'undefined')
            market = this.market (symbol);
        return this.parseOrder (response, market);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = 50, params = {}) {
        await this.loadMarkets ();
        let pageIndex = this.safeInteger (params, 'pageIndex', 1);
        const request = this.ordered ({
            'pageIndex': pageIndex,
            'pageSize': limit,
        });
        const response = await this.privatePostGetTrades (this.extend (request, params));
        let market = undefined;
        if (typeof symbol !== 'undefined') {
            market = this.market (symbol);
        }
        return this.parseTrades (response['Data'], market, since, limit);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = this.parse8601 (trade['TradeTimestampUtc']);
        let id = this.safeString (trade, 'TradeGuid');
        let orderId = this.safeString (trade, 'OrderGuid');
        let price = this.safeFloat (trade, 'Price');
        if (typeof price === 'undefined') {
            price = this.safeFloat (trade, 'SecondaryCurrencyTradePrice');
        }
        let amount = this.safeFloat (trade, 'VolumeTraded');
        if (typeof amount === 'undefined') {
            amount = this.safeFloat (trade, 'PrimaryCurrencyAmount');
        }
        let symbol = undefined;
        if (typeof market !== 'undefined')
            symbol = market['symbol'];
        let side = this.safeString (trade, 'OrderType');
        if (typeof side !== 'undefined') {
            if (side.indexOf ('Bid') >= 0)
                side = 'buy';
            else if (side.indexOf ('Offer') >= 0)
                side = 'sell';
        }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': undefined,
            'side': side,
            'price': price,
            'amount': amount,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetRecentTrades (this.extend ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
            'numberOfRecentTradesToRetrieve': 50, // max = 50
        }, params));
        return this.parseTrades (response['Trades'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let capitalizedOrderType = this.capitalize (type);
        let method = 'privatePostPlace' + capitalizedOrderType + 'Order';
        let orderType = capitalizedOrderType;
        orderType += (side === 'sell') ? 'Offer' : 'Bid';
        let order = this.ordered ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
            'orderType': orderType,
        });
        if (type === 'limit')
            order['price'] = price;
        order['volume'] = amount;
        let response = await this[method] (this.extend (order, params));
        return {
            'info': response,
            'id': response['OrderGuid'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostCancelOrder ({ 'orderGuid': id });
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length)
                url += '?' + this.urlencode (params);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            let auth = [
                url,
                'apiKey=' + this.apiKey,
                'nonce=' + nonce.toString (),
            ];
            let keys = Object.keys (params);
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i];
                auth.push (key + '=' + params[key]);
            }
            let message = auth.join (',');
            let signature = this.hmac (this.encode (message), this.encode (this.secret));
            body = this.json ({
                'apiKey': this.apiKey,
                'nonce': nonce,
                'signature': signature.toUpperCase (),
            });
            headers = { 'Content-Type': 'application/json' };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }
};
<commit_msg>independentreserve private post signature fix #3519<commit_after>'use strict';

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

const Exchange = require ('./base/Exchange');

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

module.exports = class independentreserve extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'independentreserve',
            'name': 'Independent Reserve',
            'countries': [ 'AU', 'NZ' ], // Australia, New Zealand
            'rateLimit': 1000,
            'has': {
                'CORS': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/30521662-cf3f477c-9bcb-11e7-89bc-d1ac85012eda.jpg',
                'api': {
                    'public': 'https://api.independentreserve.com/Public',
                    'private': 'https://api.independentreserve.com/Private',
                },
                'www': 'https://www.independentreserve.com',
                'doc': 'https://www.independentreserve.com/API',
            },
            'api': {
                'public': {
                    'get': [
                        'GetValidPrimaryCurrencyCodes',
                        'GetValidSecondaryCurrencyCodes',
                        'GetValidLimitOrderTypes',
                        'GetValidMarketOrderTypes',
                        'GetValidOrderTypes',
                        'GetValidTransactionTypes',
                        'GetMarketSummary',
                        'GetOrderBook',
                        'GetTradeHistorySummary',
                        'GetRecentTrades',
                        'GetFxRates',
                    ],
                },
                'private': {
                    'post': [
                        'PlaceLimitOrder',
                        'PlaceMarketOrder',
                        'CancelOrder',
                        'GetOpenOrders',
                        'GetClosedOrders',
                        'GetClosedFilledOrders',
                        'GetOrderDetails',
                        'GetAccounts',
                        'GetTransactions',
                        'GetDigitalCurrencyDepositAddress',
                        'GetDigitalCurrencyDepositAddresses',
                        'SynchDigitalCurrencyDepositAddressWithBlockchain',
                        'WithdrawDigitalCurrency',
                        'RequestFiatWithdrawal',
                        'GetTrades',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.5 / 100,
                    'maker': 0.5 / 100,
                    'percentage': true,
                    'tierBased': false,
                },
            },
        });
    }

    async fetchMarkets () {
        let baseCurrencies = await this.publicGetGetValidPrimaryCurrencyCodes ();
        let quoteCurrencies = await this.publicGetGetValidSecondaryCurrencyCodes ();
        let result = [];
        for (let i = 0; i < baseCurrencies.length; i++) {
            let baseId = baseCurrencies[i];
            let baseIdUppercase = baseId.toUpperCase ();
            let base = this.commonCurrencyCode (baseIdUppercase);
            for (let j = 0; j < quoteCurrencies.length; j++) {
                let quoteId = quoteCurrencies[j];
                let quoteIdUppercase = quoteId.toUpperCase ();
                let quote = this.commonCurrencyCode (quoteIdUppercase);
                let id = baseId + '/' + quoteId;
                let symbol = base + '/' + quote;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'info': id,
                });
            }
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let balances = await this.privatePostGetAccounts ();
        let result = { 'info': balances };
        for (let i = 0; i < balances.length; i++) {
            let balance = balances[i];
            let currencyCode = balance['CurrencyCode'];
            let uppercase = currencyCode.toUpperCase ();
            let currency = this.commonCurrencyCode (uppercase);
            let account = this.account ();
            account['free'] = balance['AvailableBalance'];
            account['total'] = balance['TotalBalance'];
            account['used'] = account['total'] - account['free'];
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetOrderBook (this.extend ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
        }, params));
        let timestamp = this.parse8601 (response['CreatedTimestampUtc']);
        return this.parseOrderBook (response, timestamp, 'BuyOrders', 'SellOrders', 'Price', 'Volume');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.parse8601 (ticker['CreatedTimestampUtc']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let last = ticker['LastPrice'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': ticker['DayHighestPrice'],
            'low': ticker['DayLowestPrice'],
            'bid': ticker['CurrentHighestBidPrice'],
            'bidVolume': undefined,
            'ask': ticker['CurrentLowestOfferPrice'],
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': ticker['DayAvgPrice'],
            'baseVolume': ticker['DayVolumeXbtInSecondaryCurrrency'],
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetMarketSummary (this.extend ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
        }, params));
        return this.parseTicker (response, market);
    }

    parseOrder (order, market = undefined) {
        let symbol = undefined;
        if (typeof market === 'undefined') {
            symbol = market['symbol'];
        } else {
            market = this.findMarket (order['PrimaryCurrencyCode'] + '/' + order['SecondaryCurrencyCode']);
        }
        let orderType = this.safeValue (order, 'Type');
        if (orderType.indexOf ('Market') >= 0)
            orderType = 'market';
        else if (orderType.indexOf ('Limit') >= 0)
            orderType = 'limit';
        let side = undefined;
        if (orderType.indexOf ('Bid') >= 0)
            side = 'buy';
        else if (orderType.indexOf ('Offer') >= 0)
            side = 'sell';
        let timestamp = this.parse8601 (order['CreatedTimestampUtc']);
        let amount = this.safeFloat (order, 'VolumeOrdered');
        if (typeof amount === 'undefined')
            amount = this.safeFloat (order, 'Volume');
        let filled = this.safeFloat (order, 'VolumeFilled');
        let remaining = undefined;
        let feeRate = this.safeFloat (order, 'FeePercent');
        let feeCost = undefined;
        if (typeof amount !== 'undefined') {
            if (typeof filled !== 'undefined') {
                remaining = amount - filled;
                if (typeof feeRate !== 'undefined')
                    feeCost = feeRate * filled;
            }
        }
        let feeCurrency = undefined;
        if (typeof market !== 'undefined') {
            symbol = market['symbol'];
            feeCurrency = market['base'];
        }
        let fee = {
            'rate': feeRate,
            'cost': feeCost,
            'currency': feeCurrency,
        };
        let id = order['OrderGuid'];
        let status = this.parseOrderStatus (order['Status']);
        let cost = this.safeFloat (order, 'Value');
        let average = this.safeFloat (order, 'AvgPrice');
        let price = this.safeFloat (order, 'Price', average);
        return {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'lastTradeTimestamp': undefined,
            'symbol': symbol,
            'type': orderType,
            'side': side,
            'price': price,
            'cost': cost,
            'average': average,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
        };
    }

    parseOrderStatus (status) {
        let statuses = {
            'Open': 'open',
            'PartiallyFilled': 'open',
            'Filled': 'closed',
            'PartiallyFilledAndCancelled': 'canceled',
            'Cancelled': 'canceled',
            'PartiallyFilledAndExpired': 'canceled',
            'Expired': 'canceled',
        };
        if (status in statuses)
            return statuses[status];
        return status;
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const response = await this.privatePostGetOrderDetails (this.extend ({
            'orderGuid': id,
        }, params));
        let market = undefined;
        if (typeof symbol !== 'undefined')
            market = this.market (symbol);
        return this.parseOrder (response, market);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = 50, params = {}) {
        await this.loadMarkets ();
        let pageIndex = this.safeInteger (params, 'pageIndex', 1);
        const request = this.ordered ({
            'pageIndex': pageIndex,
            'pageSize': limit,
        });
        const response = await this.privatePostGetTrades (this.extend (request, params));
        let market = undefined;
        if (typeof symbol !== 'undefined') {
            market = this.market (symbol);
        }
        return this.parseTrades (response['Data'], market, since, limit);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = this.parse8601 (trade['TradeTimestampUtc']);
        let id = this.safeString (trade, 'TradeGuid');
        let orderId = this.safeString (trade, 'OrderGuid');
        let price = this.safeFloat (trade, 'Price');
        if (typeof price === 'undefined') {
            price = this.safeFloat (trade, 'SecondaryCurrencyTradePrice');
        }
        let amount = this.safeFloat (trade, 'VolumeTraded');
        if (typeof amount === 'undefined') {
            amount = this.safeFloat (trade, 'PrimaryCurrencyAmount');
        }
        let symbol = undefined;
        if (typeof market !== 'undefined')
            symbol = market['symbol'];
        let side = this.safeString (trade, 'OrderType');
        if (typeof side !== 'undefined') {
            if (side.indexOf ('Bid') >= 0)
                side = 'buy';
            else if (side.indexOf ('Offer') >= 0)
                side = 'sell';
        }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': undefined,
            'side': side,
            'price': price,
            'amount': amount,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetRecentTrades (this.extend ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
            'numberOfRecentTradesToRetrieve': 50, // max = 50
        }, params));
        return this.parseTrades (response['Trades'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let capitalizedOrderType = this.capitalize (type);
        let method = 'privatePostPlace' + capitalizedOrderType + 'Order';
        let orderType = capitalizedOrderType;
        orderType += (side === 'sell') ? 'Offer' : 'Bid';
        let order = this.ordered ({
            'primaryCurrencyCode': market['baseId'],
            'secondaryCurrencyCode': market['quoteId'],
            'orderType': orderType,
        });
        if (type === 'limit')
            order['price'] = price;
        order['volume'] = amount;
        let response = await this[method] (this.extend (order, params));
        return {
            'info': response,
            'id': response['OrderGuid'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostCancelOrder ({ 'orderGuid': id });
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length)
                url += '?' + this.urlencode (params);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            let auth = [
                url,
                'apiKey=' + this.apiKey,
                'nonce=' + nonce.toString (),
            ];
            let keys = Object.keys (params);
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i];
                auth.push (key + '=' + params[key]);
            }
            let message = auth.join (',');
            let signature = this.hmac (this.encode (message), this.encode (this.secret));
            body = this.json (this.extend (this.ordered ({
                'apiKey': this.apiKey,
                'nonce': nonce,
                'signature': signature.toUpperCase (),
            }), params));
            headers = { 'Content-Type': 'application/json' };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }
};
<|endoftext|>


<commit_before>'use strict';

import fs                        from 'fs'
import gulp                      from 'gulp'
import {parseString as parseXML} from 'xml2js'
import request from 'sync-request'

import {getPostFromRSS}          from '../../app/js/utils/rssUtils'

gulp.task('configFirebase', () => {
  let firebaseJson = JSON.parse(fs.readFileSync('firebase.json'))

  /*
  * Configure Rewrites
  */
  let rewrites = []

  // create rewrites for docs
  const docFolderNames = ['posts', 'tutorials']
  docFolderNames.forEach((folderName) => {
    fs.readdirSync('app/docs/' + folderName + '/').forEach((docFilename) => {
      const key = docFilename.split('.')[0].toLowerCase()

      rewrites.push({
        'source': `/${folderName}/${key}`,
        'destination': `/docs-${folderName}-${key}.html`
      })
    })
  })

  // Create rewrites for blog
  const rssURL = 'https://blockstack-site-api.herokuapp.com/v1/blog-rss'
  const requestBody = request('GET', rssURL).body
  parseXML(requestBody, (err, blogRSSData) => {
    const firstChannel = blogRSSData.rss.channel[0]
    firstChannel.item.map((rssPost) => {
      const post = getPostFromRSS(rssPost)
      rewrites.push({
        'source': `/blog/${post.urlSlug}`,
        'destination': `/blog-${post.urlSlug}.html`
      })
    })
  })

  // Push the main index.html rewrite
  rewrites.push({
    'source': '**',
    'destination': '/index.html'
  })

  firebaseJson.hosting.rewrites = rewrites

  /*
  * Configure Redirects
  */
  const redirectRules = [
    {
      'source': '/token',
      'destination': 'https://blockstack.com'
    },
    {
      'source': '/token/',
      'destination': 'https://blockstack.com'
    },
    {
      'source': '/summit2017/',
      'destination': '/summit2017'
    },
    {
      'source': '/funding/',
      'destination': '/funding'
    },
    {
      'source': '/fund',
      'destination': '/funding'
    },
    {
      'source': '/docs/blockchain-identity',
      'destination': '/posts/blockchain-identity',
    },
    {
      'source': '/docs/blockchain-id',
      'destination': '/posts/blockchain-identity',
    },
    {
      'source': '/docs/what-is-blockstack',
      'destination': '/posts/blockstack-core',
    },
    {
      'source': '/docs/how-blockstack-works',
      'destination': '/whitepaper.pdf'
    },
    {
      'source': '/blockstack_whitepaper.pdf',
      'destination': '/whitepaper.pdf'
    },
    {
      'source': '/tokenpaper.pdf',
      'destination': 'https://blockstack.com/tokenpaper.pdf'
    },
    {
      'source': '/articles',
      'destination': '/blog'
    },
    {
      'source': '/posts',
      'destination': '/blog'
    },
    {
      'source': '/talks',
      'destination': '/videos'
    },
    {
      'source': '/browser',
      'destination': '/download'
    },
    {
      'source': '/articles/blockstack-core',
      'destination': '/papers'
    },
    {
      'source': '/articles/browser-beta',
      'destination': '/download'
    },
    {
      'source': '/articles/faq',
      'destination': '/faq'
    },
    {
      'source': '/faqs',
      'destination': '/faq'
    },
    {
      'source': '/articles/light-clients',
      'destination': '/papers'
    },
    {
      'source': '/articles/login-paper',
      'destination': '/papers'
    },
    {
      'source': '/docs/login-paper',
      'destination': '/papers'
    },
    {
      'source': '/docs/installation',
      'destination': '/tutorials'
    },
    {
      'source': '/tutorials/hello-world',
      'destination': '/tutorials/hello-blockstack',
    },
    {
      'source': '/tutorials/todo-spa',
      'destination': '/tutorials/todo-spa',
    },
    {
      'source': '/docs/cli-basic-usage',
      'destination': '/tutorials/cli-basics'
    },
    {
      'source': '/docs/cli-extended-usage',
      'destination': '/tutorials/cli-basics'
    },
    {
      'source': '/blockstack.pdf',
      'destination': '/blockstack_usenix16.pdf'
    },
    {
      'source': '/blockstack-login.pdf',
      'destination': '/blockstack_login16.pdf'
    },
    {
      'source': '/virtualchain.pdf',
      'destination': '/virtualchain_dccl16.pdf'
    },
    {
      'source': '/join',
      'destination': '/signup'
    },
    {
      'source': '/newsletter',
      'destination': '/signup'
    },
    {
      'source': '/users',
      'destination': '/signup'
    },
    {
      'source': '/summit',
      'destination': 'https://www.eventbrite.com/e/blockstack-summit-2017-tickets-35385366584'
    },
    {
      'source': '/tokensale',
      'destination': 'https://blockstack.com'
    },
    {
      'source': '/blog/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df',
      'destination': 'https://medium.com/@ryanshea/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df'
    },
    {
      'source': '/blog/forking-a-network-86d1b766d38d',
      'destination': 'https://medium.com/@muneeb/forking-a-network-86d1b766d38d'
    },
    {
      source: '/blog/blockchains-for-distributed-systems-ffd68e6341b5',
      destination: 'https://medium.com/@judecnelson/blockchains-for-distributed-systems-ffd68e6341b5'
    },
    {
      source: '/blog/blockchains-are-not-magic-4fa6dd412ab',
      destination: 'https://medium.com/@muneeb/blockchains-are-not-magic-4fa6dd412ab'
    },
    {
      source: '/blog/snapstorm-what-is-identity-ad34e681b62f',
      destination: 'https://medium.com/@ryanshea/snapstorm-what-is-identity-ad34e681b62f'
    },
    {
      source: '/blog/toasters-are-breaking-the-internet-c1d153c33f78',
      destination: 'https://medium.com/@muneeb/toasters-are-breaking-the-internet-c1d153c33f78'
    },
    {
      source: '/blog/spreading-the-gospel-of-decentralization-6a3404ebd869',
      destination: 'https://medium.com/@ryanshea/spreading-the-gospel-of-decentralization-6a3404ebd869'
    },
    {
      source: '/blog/trust-machine-global-and-federated-8b9dc6dab7ec',
      destination: 'https://medium.com/@muneeb/trust-machine-global-and-federated-8b9dc6dab7ec'
    },
    {
      source: '/blog/the-blockchain-as-a-journal-dc1715ffa8a1',
      destination: 'https://medium.com/@ryanshea/the-blockchain-as-a-journal-dc1715ffa8a1'
    },
    {
      source: '/blog/blockstack-labs-partners-with-microsoft-3ffccebf3f4f',
      destination: 'https://medium.com/@blockstackinc/blockstack-labs-partners-with-microsoft-3ffccebf3f4f'
    },
    {
      source: '/blog/video-blockstack-ipfs-berlin-meetup-a05770180d9e',
      destination: 'https://medium.com/@ryanshea/video-blockstack-ipfs-berlin-meetup-a05770180d9e'
    },
    {
      source: '/blog/blockstack-meetups-come-to-asia-1df29454eb2c',
      destination: 'https://medium.com/@larrysalibra/blockstack-meetups-come-to-asia-1df29454eb2c'
    },
    {
      source: '/blog/snapstorm-decentralized-apps-and-decentralization-4084e8e432bf',
      destination: 'https://medium.com/@ryanshea/snapstorm-decentralized-apps-and-decentralization-4084e8e432bf'
    },
    {
      source: '/blog/blockstack-core-v0-14-0-release-aad748f46d',
      destination: '/blog/blockstack-core-v0-14-release'
    },
    {
      source: '/blog/blockstack-at-the-decentralized-web-summit-8d45e3ea4b82',
      destination: 'https://medium.com/@guylepage3/blockstack-at-the-decentralized-web-summit-8d45e3ea4b82'
    },
    {
      source: '/blog/blockstack-summit-2015-review-part-1-b710ada61c70',
      destination: 'https://medium.com/@lightcoin/blockstack-summit-2015-review-part-1-b710ada61c70'
    },
    {
      source: '/blog/blockstack-summit-2015-review-part-2-38e73f19e4a5',
      destination: 'https://medium.com/@lightcoin/blockstack-summit-2015-review-part-2-38e73f19e4a5'
    },
    {
      source: '/blog/peeling-the-layers-of-blockchain-forks-d06b52340a3c',
      destination: 'https://medium.com/@muneeb/peeling-the-layers-of-blockchain-forks-d06b52340a3c'
    },
    {
      source: '/blog/the-comprehensive-speaker-guide-to-consensus-2016-ba356dab8886',
      destination: 'https://medium.com/@ryanshea/the-comprehensive-speaker-guide-to-consensus-2016-ba356dab8886'
    },
    {
      source: '/blog/next-steps-towards-a-secure-internet-a057217acebb',
      destination: 'https://medium.com/@muneeb/next-steps-towards-a-secure-internet-a057217acebb'
    },
    {
      source: '/blog/this-months-top-blockstack-tweets-september-a829118dafa7',
      destination: 'https://medium.com/@ryanshea/this-months-top-blockstack-tweets-september-a829118dafa7'
    },
    {
      source: '/blog/blockstack-research-paper-published-at-usenix-atc16-conference-34bad5bd0221',
      destination: '/blog/blockstack-research-paper-published-at-usenix-atc16-conference'
    },
    {
      source: '/blog/it-s-not-about-privacy-phone-protection-21b0d4f400d5',
      destination: 'https://medium.com/@ryanshea/it-s-not-about-privacy-phone-protection-21b0d4f400d5'
    },
    {
      source: '/blog/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df',
      destination: 'https://medium.com/@ryanshea/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df'
    },
    {
      source: '/blog/what-s-at-stake-in-the-bitcoin-block-size-debate-70197e8bc983',
      destination: 'https://medium.com/@lightcoin/what-s-at-stake-in-the-bitcoin-block-size-debate-70197e8bc983'
    },
    {
      source: '/blog/things-i-think-i-know-about-blockchains-public-and-private-828467f43707',
      destination: 'https://medium.com/@ryanshea/things-i-think-i-know-about-blockchains-public-and-private-828467f43707'
    },
    {
      source: '/blog/solar-storm-a-serious-security-exploit-with-ethereum-not-just-the-dao-a03d797d98fa',
      destination: 'https://medium.com/@muneeb/solar-storm-a-serious-security-exploit-with-ethereum-not-just-the-dao-a03d797d98fa'
    },
    {
      source: '/blog/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a',
      destination: 'https://medium.com/@ryanshea/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a'
    },
    {
      source: '/blog/strange-democracy-fd00b1bc1f8',
      destination: 'https://medium.com/@cryptowanderer/strange-democracy-fd00b1bc1f8'
    },
    {
      source: '/blog/the-road-ahead-for-ethereum-b5b090bcd1a',
      destination: 'https://medium.com/@muneeb/the-road-ahead-for-ethereum-b5b090bcd1a'
    },
    {
      source: '/blog/gaming-the-dao-prof-emin-g%C3%BCn-sirers-cornell-talk-d0b78099d0d5',
      destination: 'https://medium.com/@guylepage3/gaming-the-dao-prof-emin-g%C3%BCn-sirers-cornell-talk-d0b78099d0d5'
    }
  ]

  let redirects = []

  redirectRules.forEach((rule) => {
    redirects.push({
      'source': rule.source,
      'destination': rule.destination,
      'type': 301
    })
  })

  firebaseJson.hosting.redirects = redirects

  fs.writeFileSync('firebase.json', JSON.stringify(firebaseJson, null, 2))

  const netlifyRedirectFilename = 'build/_redirects'
  fs.writeFileSync(netlifyRedirectFilename, '# Netlify redirects & rewrites \n')

  firebaseJson.hosting.redirects.forEach(redirect => {
    const line = `${redirect.source} ${redirect.destination} ${redirect.type} \n`
    fs.appendFileSync(netlifyRedirectFilename, line)
  })

  firebaseJson.hosting.rewrites.forEach(rewrite => {
    if (rewrite.source === '**') {
      const line = `/*    /index.html   200 \n`
      fs.appendFileSync(netlifyRedirectFilename, line)
    } else {
      const line = `${rewrite.source} ${rewrite.destination} 200 \n`
      fs.appendFileSync(netlifyRedirectFilename, line)
    }
  })
});
<commit_msg>add comments #486<commit_after>'use strict';

import fs                        from 'fs'
import gulp                      from 'gulp'
import {parseString as parseXML} from 'xml2js'
import request from 'sync-request'

import {getPostFromRSS}          from '../../app/js/utils/rssUtils'

gulp.task('configFirebase', () => {
  let firebaseJson = JSON.parse(fs.readFileSync('firebase.json'))

  /*
  * Configure Rewrites
  */
  let rewrites = []

  // create rewrites for docs
  const docFolderNames = ['posts', 'tutorials']
  docFolderNames.forEach((folderName) => {
    fs.readdirSync('app/docs/' + folderName + '/').forEach((docFilename) => {
      const key = docFilename.split('.')[0].toLowerCase()

      rewrites.push({
        'source': `/${folderName}/${key}`,
        'destination': `/docs-${folderName}-${key}.html`
      })
    })
  })

  // Create rewrites for blog
  const rssURL = 'https://blockstack-site-api.herokuapp.com/v1/blog-rss'
  const requestBody = request('GET', rssURL).body
  parseXML(requestBody, (err, blogRSSData) => {
    const firstChannel = blogRSSData.rss.channel[0]
    firstChannel.item.map((rssPost) => {
      const post = getPostFromRSS(rssPost)
      rewrites.push({
        'source': `/blog/${post.urlSlug}`,
        'destination': `/blog-${post.urlSlug}.html`
      })
    })
  })

  // Push the main index.html rewrite
  rewrites.push({
    'source': '**',
    'destination': '/index.html'
  })

  firebaseJson.hosting.rewrites = rewrites

  /*
  * Configure Redirects
  */
  const redirectRules = [
    {
      'source': '/token',
      'destination': 'https://blockstack.com'
    },
    {
      'source': '/token/',
      'destination': 'https://blockstack.com'
    },
    {
      'source': '/summit2017/',
      'destination': '/summit2017'
    },
    {
      'source': '/funding/',
      'destination': '/funding'
    },
    {
      'source': '/fund',
      'destination': '/funding'
    },
    {
      'source': '/docs/blockchain-identity',
      'destination': '/posts/blockchain-identity',
    },
    {
      'source': '/docs/blockchain-id',
      'destination': '/posts/blockchain-identity',
    },
    {
      'source': '/docs/what-is-blockstack',
      'destination': '/posts/blockstack-core',
    },
    {
      'source': '/docs/how-blockstack-works',
      'destination': '/whitepaper.pdf'
    },
    {
      'source': '/blockstack_whitepaper.pdf',
      'destination': '/whitepaper.pdf'
    },
    {
      'source': '/tokenpaper.pdf',
      'destination': 'https://blockstack.com/tokenpaper.pdf'
    },
    {
      'source': '/articles',
      'destination': '/blog'
    },
    {
      'source': '/posts',
      'destination': '/blog'
    },
    {
      'source': '/talks',
      'destination': '/videos'
    },
    {
      'source': '/browser',
      'destination': '/download'
    },
    {
      'source': '/articles/blockstack-core',
      'destination': '/papers'
    },
    {
      'source': '/articles/browser-beta',
      'destination': '/download'
    },
    {
      'source': '/articles/faq',
      'destination': '/faq'
    },
    {
      'source': '/faqs',
      'destination': '/faq'
    },
    {
      'source': '/articles/light-clients',
      'destination': '/papers'
    },
    {
      'source': '/articles/login-paper',
      'destination': '/papers'
    },
    {
      'source': '/docs/login-paper',
      'destination': '/papers'
    },
    {
      'source': '/docs/installation',
      'destination': '/tutorials'
    },
    {
      'source': '/tutorials/hello-world',
      'destination': '/tutorials/hello-blockstack',
    },
    {
      'source': '/tutorials/todo-spa',
      'destination': '/tutorials/todo-spa',
    },
    {
      'source': '/docs/cli-basic-usage',
      'destination': '/tutorials/cli-basics'
    },
    {
      'source': '/docs/cli-extended-usage',
      'destination': '/tutorials/cli-basics'
    },
    {
      'source': '/blockstack.pdf',
      'destination': '/blockstack_usenix16.pdf'
    },
    {
      'source': '/blockstack-login.pdf',
      'destination': '/blockstack_login16.pdf'
    },
    {
      'source': '/virtualchain.pdf',
      'destination': '/virtualchain_dccl16.pdf'
    },
    {
      'source': '/join',
      'destination': '/signup'
    },
    {
      'source': '/newsletter',
      'destination': '/signup'
    },
    {
      'source': '/users',
      'destination': '/signup'
    },
    {
      'source': '/summit',
      'destination': 'https://www.eventbrite.com/e/blockstack-summit-2017-tickets-35385366584'
    },
    {
      'source': '/tokensale',
      'destination': 'https://blockstack.com'
    },
    {
      'source': '/blog/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df',
      'destination': 'https://medium.com/@ryanshea/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df'
    },
    {
      'source': '/blog/forking-a-network-86d1b766d38d',
      'destination': 'https://medium.com/@muneeb/forking-a-network-86d1b766d38d'
    },
    {
      source: '/blog/blockchains-for-distributed-systems-ffd68e6341b5',
      destination: 'https://medium.com/@judecnelson/blockchains-for-distributed-systems-ffd68e6341b5'
    },
    {
      source: '/blog/blockchains-are-not-magic-4fa6dd412ab',
      destination: 'https://medium.com/@muneeb/blockchains-are-not-magic-4fa6dd412ab'
    },
    {
      source: '/blog/snapstorm-what-is-identity-ad34e681b62f',
      destination: 'https://medium.com/@ryanshea/snapstorm-what-is-identity-ad34e681b62f'
    },
    {
      source: '/blog/toasters-are-breaking-the-internet-c1d153c33f78',
      destination: 'https://medium.com/@muneeb/toasters-are-breaking-the-internet-c1d153c33f78'
    },
    {
      source: '/blog/spreading-the-gospel-of-decentralization-6a3404ebd869',
      destination: 'https://medium.com/@ryanshea/spreading-the-gospel-of-decentralization-6a3404ebd869'
    },
    {
      source: '/blog/trust-machine-global-and-federated-8b9dc6dab7ec',
      destination: 'https://medium.com/@muneeb/trust-machine-global-and-federated-8b9dc6dab7ec'
    },
    {
      source: '/blog/the-blockchain-as-a-journal-dc1715ffa8a1',
      destination: 'https://medium.com/@ryanshea/the-blockchain-as-a-journal-dc1715ffa8a1'
    },
    {
      source: '/blog/blockstack-labs-partners-with-microsoft-3ffccebf3f4f',
      destination: 'https://medium.com/@blockstackinc/blockstack-labs-partners-with-microsoft-3ffccebf3f4f'
    },
    {
      source: '/blog/video-blockstack-ipfs-berlin-meetup-a05770180d9e',
      destination: 'https://medium.com/@ryanshea/video-blockstack-ipfs-berlin-meetup-a05770180d9e'
    },
    {
      source: '/blog/blockstack-meetups-come-to-asia-1df29454eb2c',
      destination: 'https://medium.com/@larrysalibra/blockstack-meetups-come-to-asia-1df29454eb2c'
    },
    {
      source: '/blog/snapstorm-decentralized-apps-and-decentralization-4084e8e432bf',
      destination: 'https://medium.com/@ryanshea/snapstorm-decentralized-apps-and-decentralization-4084e8e432bf'
    },
    {
      source: '/blog/blockstack-core-v0-14-0-release-aad748f46d',
      destination: '/blog/blockstack-core-v0-14-release'
    },
    {
      source: '/blog/blockstack-at-the-decentralized-web-summit-8d45e3ea4b82',
      destination: 'https://medium.com/@guylepage3/blockstack-at-the-decentralized-web-summit-8d45e3ea4b82'
    },
    {
      source: '/blog/blockstack-summit-2015-review-part-1-b710ada61c70',
      destination: 'https://medium.com/@lightcoin/blockstack-summit-2015-review-part-1-b710ada61c70'
    },
    {
      source: '/blog/blockstack-summit-2015-review-part-2-38e73f19e4a5',
      destination: 'https://medium.com/@lightcoin/blockstack-summit-2015-review-part-2-38e73f19e4a5'
    },
    {
      source: '/blog/peeling-the-layers-of-blockchain-forks-d06b52340a3c',
      destination: 'https://medium.com/@muneeb/peeling-the-layers-of-blockchain-forks-d06b52340a3c'
    },
    {
      source: '/blog/the-comprehensive-speaker-guide-to-consensus-2016-ba356dab8886',
      destination: 'https://medium.com/@ryanshea/the-comprehensive-speaker-guide-to-consensus-2016-ba356dab8886'
    },
    {
      source: '/blog/next-steps-towards-a-secure-internet-a057217acebb',
      destination: 'https://medium.com/@muneeb/next-steps-towards-a-secure-internet-a057217acebb'
    },
    {
      source: '/blog/this-months-top-blockstack-tweets-september-a829118dafa7',
      destination: 'https://medium.com/@ryanshea/this-months-top-blockstack-tweets-september-a829118dafa7'
    },
    {
      source: '/blog/blockstack-research-paper-published-at-usenix-atc16-conference-34bad5bd0221',
      destination: '/blog/blockstack-research-paper-published-at-usenix-atc16-conference'
    },
    {
      source: '/blog/it-s-not-about-privacy-phone-protection-21b0d4f400d5',
      destination: 'https://medium.com/@ryanshea/it-s-not-about-privacy-phone-protection-21b0d4f400d5'
    },
    {
      source: '/blog/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df',
      destination: 'https://medium.com/@ryanshea/the-scaling-announcement-bitcoin-core-should-have-made-146790f755df'
    },
    {
      source: '/blog/what-s-at-stake-in-the-bitcoin-block-size-debate-70197e8bc983',
      destination: 'https://medium.com/@lightcoin/what-s-at-stake-in-the-bitcoin-block-size-debate-70197e8bc983'
    },
    {
      source: '/blog/things-i-think-i-know-about-blockchains-public-and-private-828467f43707',
      destination: 'https://medium.com/@ryanshea/things-i-think-i-know-about-blockchains-public-and-private-828467f43707'
    },
    {
      source: '/blog/solar-storm-a-serious-security-exploit-with-ethereum-not-just-the-dao-a03d797d98fa',
      destination: 'https://medium.com/@muneeb/solar-storm-a-serious-security-exploit-with-ethereum-not-just-the-dao-a03d797d98fa'
    },
    {
      source: '/blog/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a',
      destination: 'https://medium.com/@ryanshea/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a'
    },
    {
      source: '/blog/strange-democracy-fd00b1bc1f8',
      destination: 'https://medium.com/@cryptowanderer/strange-democracy-fd00b1bc1f8'
    },
    {
      source: '/blog/the-road-ahead-for-ethereum-b5b090bcd1a',
      destination: 'https://medium.com/@muneeb/the-road-ahead-for-ethereum-b5b090bcd1a'
    },
    {
      source: '/blog/gaming-the-dao-prof-emin-g%C3%BCn-sirers-cornell-talk-d0b78099d0d5',
      destination: 'https://medium.com/@guylepage3/gaming-the-dao-prof-emin-g%C3%BCn-sirers-cornell-talk-d0b78099d0d5'
    }
  ]

  let redirects = []

  redirectRules.forEach((rule) => {
    redirects.push({
      'source': rule.source,
      'destination': rule.destination,
      'type': 301
    })
  })

  firebaseJson.hosting.redirects = redirects

  fs.writeFileSync('firebase.json', JSON.stringify(firebaseJson, null, 2))

  // Convert firebase redirects and rewrites into a netlify compatible format

  const netlifyRedirectFilename = 'build/_redirects'
  fs.writeFileSync(netlifyRedirectFilename, '# Netlify redirects & rewrites \n')

  firebaseJson.hosting.redirects.forEach(redirect => {
    const line = `${redirect.source} ${redirect.destination} ${redirect.type} \n`
    fs.appendFileSync(netlifyRedirectFilename, line)
  })

  firebaseJson.hosting.rewrites.forEach(rewrite => {
    if (rewrite.source === '**') { // Single page app / history push state redirect
      const line = `/*    /index.html   200 \n`
      fs.appendFileSync(netlifyRedirectFilename, line)
    } else {
      const line = `${rewrite.source} ${rewrite.destination} 200 \n`
      fs.appendFileSync(netlifyRedirectFilename, line)
    }
  })
});
<|endoftext|>


<commit_before>/*!
 * jQuery Mobile v@VERSION
 * http://jquerymobile.com/
 *
 * Copyright 2010, jQuery Project
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */

(function( $, window, undefined ) {

	//jQuery.mobile configurable options
	$.extend( $.mobile, {

		//namespace used framework-wide for data-attrs. Default is no namespace
		ns: "",

		//define the url parameter used for referencing widget-generated sub-pages.
		//Translates to to example.html&ui-page=subpageIdentifier
		//hash segment before &ui-page= is used to make Ajax request
		subPageUrlKey: "ui-page",

		//anchor links with a data-rel, or pages with a  data-role, that match these selectors will be untrackable in history
		//(no change in URL, not bookmarkable)
		nonHistorySelectors: "dialog",

		//class assigned to page currently in view, and during transitions
		activePageClass: "ui-page-active",

		//class used for "active" button state, from CSS framework
		activeBtnClass: "ui-btn-active",

		//automatically handle clicks and form submissions through Ajax, when same-domain
		ajaxEnabled: true,

		//automatically load and show pages based on location.hash
		hashListeningEnabled: true,

		// TODO: deprecated - remove at 1.0
		//automatically handle link clicks through Ajax, when possible
		ajaxLinksEnabled: true,

		// TODO: deprecated - remove at 1.0
		//automatically handle form submissions through Ajax, when possible
		ajaxFormsEnabled: true,

		//set default transition - 'none' for no transitions
		defaultTransition: "slide",

		//show loading message during Ajax requests
		//if false, message will not appear, but loading classes will still be toggled on html el
		loadingMessage: "loading",

		//error response message - appears when an Ajax page request fails
		pageLoadErrorMessage: "Error Loading Page",

		//configure meta viewport tag's content attr:
		//note: this feature is deprecated in A4 in favor of adding
		//the meta viewport element directly in the markup
		metaViewportContent: "width=device-width, minimum-scale=1, maximum-scale=1",

		//support conditions that must be met in order to proceed
		//default enhanced qualifications are media query support OR IE 7+
		gradeA: function(){
			return $.support.mediaquery || $.mobile.browser.ie && $.mobile.browser.ie >= 7;
		},

		//TODO might be useful upstream in jquery itself ?
		keyCode: {
			ALT: 18,
			BACKSPACE: 8,
			CAPS_LOCK: 20,
			COMMA: 188,
			COMMAND: 91,
			COMMAND_LEFT: 91, // COMMAND
			COMMAND_RIGHT: 93,
			CONTROL: 17,
			DELETE: 46,
			DOWN: 40,
			END: 35,
			ENTER: 13,
			ESCAPE: 27,
			HOME: 36,
			INSERT: 45,
			LEFT: 37,
			MENU: 93, // COMMAND_RIGHT
			NUMPAD_ADD: 107,
			NUMPAD_DECIMAL: 110,
			NUMPAD_DIVIDE: 111,
			NUMPAD_ENTER: 108,
			NUMPAD_MULTIPLY: 106,
			NUMPAD_SUBTRACT: 109,
			PAGE_DOWN: 34,
			PAGE_UP: 33,
			PERIOD: 190,
			RIGHT: 39,
			SHIFT: 16,
			SPACE: 32,
			TAB: 9,
			UP: 38,
			WINDOWS: 91 // COMMAND
		},

		//scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
		silentScroll: function( ypos ) {
			ypos = ypos || 0;
			// prevent scrollstart and scrollstop events
			$.event.special.scrollstart.enabled = false;

			setTimeout(function() {
				window.scrollTo( 0, ypos );
				$(document).trigger( "silentscroll", { x: 0, y: ypos });
			},20);

			setTimeout(function() {
				$.event.special.scrollstart.enabled = true;
			}, 150 );
		}
	});

	//mobile version of data and removeData and hasData methods
	//ensures all data is set and retrieved using jQuery Mobile's data namespace
    $.fn.jqmData = function( prop, value ){
    	return this.data( prop ? $.mobile.ns + prop : prop, value );
    };

    $.jqmData = function( elem, prop, value ){
    	return $.data( elem, prop && $.mobile.ns + prop, value );
    };

    $.fn.jqmRemoveData = function( prop ){
    	return this.removeData( $.mobile.ns + prop );
    };

    $.jqmRemoveData = function( elem, prop ){
    	return $.removeData( elem, prop && $.mobile.ns + prop );
    };

    $.jqmHasData = function( elem, prop ){
    	return $.hasData( elem, prop && $.mobile.ns + prop );
    };

	// Monkey-patching Sizzle to filter the :jqmData selector
	var oldFind = $.find;

	$.find = function( selector, context, ret, extra ) {
		selector = selector.replace(/:jqmData\(([^)]*)\)/g, "[data-" + ($.mobile.ns || "") + "$1]");

		return oldFind.call( this, selector, context, ret, extra );
	};

	$.extend( $.find, oldFind );

	$.find.matches = function( expr, set ) {
		return $.find( expr, null, null, set );
	};

	$.find.matchesSelector = function( node, expr ) {
		return $.find( expr, null, null, [node] ).length > 0;
	};
})( jQuery, this );
<commit_msg>whitespace in mobile core<commit_after>/*!
 * jQuery Mobile v@VERSION
 * http://jquerymobile.com/
 *
 * Copyright 2010, jQuery Project
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */

(function( $, window, undefined ) {

	//jQuery.mobile configurable options
	$.extend( $.mobile, {

		//namespace used framework-wide for data-attrs. Default is no namespace
		ns: "",

		//define the url parameter used for referencing widget-generated sub-pages.
		//Translates to to example.html&ui-page=subpageIdentifier
		//hash segment before &ui-page= is used to make Ajax request
		subPageUrlKey: "ui-page",

		//anchor links with a data-rel, or pages with a  data-role, that match these selectors will be untrackable in history
		//(no change in URL, not bookmarkable)
		nonHistorySelectors: "dialog",

		//class assigned to page currently in view, and during transitions
		activePageClass: "ui-page-active",

		//class used for "active" button state, from CSS framework
		activeBtnClass: "ui-btn-active",

		//automatically handle clicks and form submissions through Ajax, when same-domain
		ajaxEnabled: true,

		//automatically load and show pages based on location.hash
		hashListeningEnabled: true,

		// TODO: deprecated - remove at 1.0
		//automatically handle link clicks through Ajax, when possible
		ajaxLinksEnabled: true,

		// TODO: deprecated - remove at 1.0
		//automatically handle form submissions through Ajax, when possible
		ajaxFormsEnabled: true,

		//set default transition - 'none' for no transitions
		defaultTransition: "slide",

		//show loading message during Ajax requests
		//if false, message will not appear, but loading classes will still be toggled on html el
		loadingMessage: "loading",

		//error response message - appears when an Ajax page request fails
		pageLoadErrorMessage: "Error Loading Page",

		//configure meta viewport tag's content attr:
		//note: this feature is deprecated in A4 in favor of adding
		//the meta viewport element directly in the markup
		metaViewportContent: "width=device-width, minimum-scale=1, maximum-scale=1",

		//support conditions that must be met in order to proceed
		//default enhanced qualifications are media query support OR IE 7+
		gradeA: function(){
			return $.support.mediaquery || $.mobile.browser.ie && $.mobile.browser.ie >= 7;
		},

		//TODO might be useful upstream in jquery itself ?
		keyCode: {
			ALT: 18,
			BACKSPACE: 8,
			CAPS_LOCK: 20,
			COMMA: 188,
			COMMAND: 91,
			COMMAND_LEFT: 91, // COMMAND
			COMMAND_RIGHT: 93,
			CONTROL: 17,
			DELETE: 46,
			DOWN: 40,
			END: 35,
			ENTER: 13,
			ESCAPE: 27,
			HOME: 36,
			INSERT: 45,
			LEFT: 37,
			MENU: 93, // COMMAND_RIGHT
			NUMPAD_ADD: 107,
			NUMPAD_DECIMAL: 110,
			NUMPAD_DIVIDE: 111,
			NUMPAD_ENTER: 108,
			NUMPAD_MULTIPLY: 106,
			NUMPAD_SUBTRACT: 109,
			PAGE_DOWN: 34,
			PAGE_UP: 33,
			PERIOD: 190,
			RIGHT: 39,
			SHIFT: 16,
			SPACE: 32,
			TAB: 9,
			UP: 38,
			WINDOWS: 91 // COMMAND
		},

		//scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
		silentScroll: function( ypos ) {
			ypos = ypos || 0;
			// prevent scrollstart and scrollstop events
			$.event.special.scrollstart.enabled = false;

			setTimeout(function() {
				window.scrollTo( 0, ypos );
				$(document).trigger( "silentscroll", { x: 0, y: ypos });
			},20);

			setTimeout(function() {
				$.event.special.scrollstart.enabled = true;
			}, 150 );
		}
	});

	//mobile version of data and removeData and hasData methods
	//ensures all data is set and retrieved using jQuery Mobile's data namespace
  $.fn.jqmData = function( prop, value ){
    return this.data( prop ? $.mobile.ns + prop : prop, value );
  };

  $.jqmData = function( elem, prop, value ){
    return $.data( elem, prop && $.mobile.ns + prop, value );
  };

  $.fn.jqmRemoveData = function( prop ){
    return this.removeData( $.mobile.ns + prop );
  };

  $.jqmRemoveData = function( elem, prop ){
    return $.removeData( elem, prop && $.mobile.ns + prop );
  };

  $.jqmHasData = function( elem, prop ){
    return $.hasData( elem, prop && $.mobile.ns + prop );
  };

	// Monkey-patching Sizzle to filter the :jqmData selector
	var oldFind = $.find;

	$.find = function( selector, context, ret, extra ) {
		selector = selector.replace(/:jqmData\(([^)]*)\)/g, "[data-" + ($.mobile.ns || "") + "$1]");

		return oldFind.call( this, selector, context, ret, extra );
	};

	$.extend( $.find, oldFind );

	$.find.matches = function( expr, set ) {
		return $.find( expr, null, null, set );
	};

	$.find.matchesSelector = function( node, expr ) {
		return $.find( expr, null, null, [node] ).length > 0;
	};
})( jQuery, this );
<|endoftext|>


<commit_before>//==== Group Controller ====//
pbpApp.controller('pbpController', ["$scope", "$http", "groupServices", "$location", "$rootScope", function($scope, $http, groupServices, $location, $rootScope) {
    //Group list
    $scope.groups = [];
    //Load ajax only first time
    if(groupServices.groups == null) {
        // The groupServices returns a promise.
        groupServices.getGroups()
        .then(
            function( groups ) {
                 $scope.groups = groups;
            }
        );
    } else {
        $scope.groups = groupServices.groups;
    }
    //Update groups in service when scope change
    $scope.$watch("groups", function() {
        groupServices.groups = $scope.groups;
    });
    //Remove group
    $scope.removeGroup = function(group) {
        if(!confirm("Are you really want to delete this product?")) {
            return false;
        }
        groupServices.removeGroup(group)
        .then(function(response) {
            $scope.groups = response;
        }, function(error) {
            console.warn(error);
        });
    }
    //Update groups whenever groupServices.groups change
    $scope.$on('handleUpdateGroups', function() {
        $scope.groups = groupServices.groups;
    });
    // Used layer
    $scope.mediaUrl = angular.element(document.querySelector("#mst_media_url")).val() + "pbp/images/";
    console.log("Used Layer");
    $rootScope.layerStack = {};
}]);
//=== ADD group controller ===//
pbpApp.controller('addGroupController', ["$scope", "groupServices", "$location", function($scope, groupServices, $location) {
    $scope.groupData = {
        id: 0,
        title: 'Unknow',
        base_product_id : '',
        description: 'Defaut description',
        status: '1',
    }
    $scope.saveGroup = function() {
        groupServices.addGroup($scope.groupData)
        .then(function(response) {
            groupServices.groups = response;
            $location.path('/');
        }, function(error) {
            console.warn(error);
        });
    }
}]);
//==== ADD LAYER ====//
pbpApp.controller('addLayerController', ["$scope", "groupServices", "$location", "$routeParams", function($scope, groupServices, $location, $routeParams) {
    $scope.groups = [];
    $scope.parents = [];
    $scope.id = $routeParams.id;
    //If admin reload add layer page, redirect to main page
    if($scope.id !== '0' && groupServices.currentLayer === null) {
        $location.path("/");
        return;
    }
    //Load ajax only first time
    if(groupServices.groups === null) {
        // The groupServices returns a promise.
        groupServices.getGroups()
        .then(
            function( groups ) {
                $scope.groups = groups;
                groupServices.groups = $scope.groups;
            }
        );
    } else {
        $scope.groups = groupServices.groups;
    }
    //Load parents same as group
    if(groupServices.parents === null) {
        // The groupServices returns a promise.
        groupServices.getLayerParents()
        .then(
            function( parents ) {
                $scope.parents = parents;
                groupServices.parents = $scope.parents;
            }
        );
    } else {
        $scope.parents = groupServices.parents;
    }
    // Use pdp-media directive
    $scope.thumbnail_image = (groupServices.currentLayer) ? groupServices.currentLayer.thumbnail_image : '';
    $scope.main_image = (groupServices.currentLayer) ? groupServices.currentLayer.main_image : '';
    $scope.layerData = {
        id: 0,
        title: '',
        parent_id: '0',
        group_id: '',
        description: '',
        thumbnail_image: $scope.thumbnail_image,
        main_image: $scope.main_image,
        price: 0,
        position: 0,
        is_required: '2',
        status: '1',
    }
    if($scope.id !== 0 && groupServices.currentLayer) {
        $scope.layerData = groupServices.currentLayer;
    }
    // Binding data from child scope
    $scope.$watch("thumbnail_image", function() {
        $scope.layerData.thumbnail_image = $scope.thumbnail_image;
    });
    $scope.$watch("main_image", function() {
        $scope.layerData.main_image = $scope.main_image;
    });
    //Save layer function
    $scope.isSaving = false;
    $scope.saveLayer = function() {
        $scope.isSaving = true;
        if(!$scope.validForm()) {
            return false;
        }
        groupServices.addLayer($scope.layerData)
        .then(function(response) {
            try {
                //Reset the form
                document.getElementById("add_new_group_form").reset();
                //$scope.parents = response.parents;
                groupServices.parents = response.parents;
                groupServices.groups = response.groups;
                //Redirect to index page
                $location.path("/");
            } catch(error) {
                console.log(error);
            }
            $scope.isSaving = false;
        }, function(error) {
            console.warn(error);
        });
    }
    //Validate form
    $scope.validForm = function() {
        if(!$scope.layerData.title || !$scope.layerData.group_id || !$scope.layerData.parent_id) {
            return false;
        }
        return true;
    }
}]);
<commit_msg>reset layerData when addLayer<commit_after>//==== Group Controller ====//
pbpApp.controller('pbpController', ["$scope", "$http", "groupServices", "$location", "$rootScope", function($scope, $http, groupServices, $location, $rootScope) {
    //Group list
    $scope.groups = [];
    //Load ajax only first time
    if(groupServices.groups == null) {
        // The groupServices returns a promise.
        groupServices.getGroups()
        .then(
            function( groups ) {
                 $scope.groups = groups;
            }
        );
    } else {
        $scope.groups = groupServices.groups;
    }
    //Update groups in service when scope change
    $scope.$watch("groups", function() {
        groupServices.groups = $scope.groups;
    });
    //Remove group
    $scope.removeGroup = function(group) {
        if(!confirm("Are you really want to delete this product?")) {
            return false;
        }
        groupServices.removeGroup(group)
        .then(function(response) {
            $scope.groups = response;
        }, function(error) {
            console.warn(error);
        });
    }
    //Update groups whenever groupServices.groups change
    $scope.$on('handleUpdateGroups', function() {
        $scope.groups = groupServices.groups;
    });
    // Used layer
    $scope.mediaUrl = angular.element(document.querySelector("#mst_media_url")).val() + "pbp/images/";
    console.log("Used Layer");
    $rootScope.layerStack = {};
}]);
//=== ADD group controller ===//
pbpApp.controller('addGroupController', ["$scope", "groupServices", "$location", function($scope, groupServices, $location) {
    $scope.groupData = {
        id: 0,
        title: 'Unknow',
        base_product_id : '',
        description: 'Defaut description',
        status: '1',
    }
    $scope.saveGroup = function() {
        groupServices.addGroup($scope.groupData)
        .then(function(response) {
            groupServices.groups = response;
            $location.path('/');
        }, function(error) {
            console.warn(error);
        });
    }
}]);
//==== ADD LAYER ====//
pbpApp.controller('addLayerController', ["$scope", "groupServices", "$location", "$routeParams", function($scope, groupServices, $location, $routeParams) {
    $scope.groups = [];
    $scope.parents = [];
    $scope.id = $routeParams.id;
    //If admin reload add layer page, redirect to main page
    if($scope.id !== '0' && groupServices.currentLayer === null) {
        $location.path("/");
        return;
    }
    //Load ajax only first time
    if(groupServices.groups === null) {
        // The groupServices returns a promise.
        groupServices.getGroups()
        .then(
            function( groups ) {
                $scope.groups = groups;
                groupServices.groups = $scope.groups;
            }
        );
    } else {
        $scope.groups = groupServices.groups;
    }
    //Load parents same as group
    if(groupServices.parents === null) {
        // The groupServices returns a promise.
        groupServices.getLayerParents()
        .then(
            function( parents ) {
                $scope.parents = parents;
                groupServices.parents = $scope.parents;
            }
        );
    } else {
        $scope.parents = groupServices.parents;
    }
    // Use pdp-media directive
    $scope.thumbnail_image = (groupServices.currentLayer) ? groupServices.currentLayer.thumbnail_image : '';
    $scope.main_image = (groupServices.currentLayer) ? groupServices.currentLayer.main_image : '';
    $scope.layerData = {
        id: 0,
        title: '',
        parent_id: '0',
        group_id: '',
        description: '',
        thumbnail_image: $scope.thumbnail_image,
        main_image: $scope.main_image,
        price: 0,
        position: 0,
        is_required: '2',
        status: '1',
    }
    if($scope.id !== '0' && groupServices.currentLayer) {
        $scope.layerData = groupServices.currentLayer;
    } else {
        //Reset $scope.layerData
        $scope.layerData = {
            id: 0,
            title: '',
            parent_id: '0',
            group_id: '',
            description: '',
            thumbnail_image: $scope.thumbnail_image,
            main_image: $scope.main_image,
            price: 0,
            position: 0,
            is_required: '2',
            status: '1',
        }
    }
    // Binding data from child scope
    $scope.$watch("thumbnail_image", function() {
        $scope.layerData.thumbnail_image = $scope.thumbnail_image;
    });
    $scope.$watch("main_image", function() {
        $scope.layerData.main_image = $scope.main_image;
    });
    //Save layer function
    $scope.isSaving = false;
    $scope.saveLayer = function() {
        $scope.isSaving = true;
        if(!$scope.validForm()) {
            return false;
        }
        groupServices.addLayer($scope.layerData)
        .then(function(response) {
            try {
                //Reset the form
                document.getElementById("add_new_group_form").reset();
                //$scope.parents = response.parents;
                groupServices.parents = response.parents;
                groupServices.groups = response.groups;
                //Redirect to index page
                $location.path("/");
            } catch(error) {
                console.log(error);
            }
            $scope.isSaving = false;
        }, function(error) {
            console.warn(error);
        });
    }
    //Validate form
    $scope.validForm = function() {
        if(!$scope.layerData.title || !$scope.layerData.group_id || !$scope.layerData.parent_id) {
            return false;
        }
        return true;
    }
}]);
<|endoftext|>


<commit_before>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.1.3): util/scrollBar.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import SelectorEngine from '../dom/selector-engine'
import Manipulator from '../dom/manipulator'
import { isElement } from './index'

/**
 * Constants
 */

const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
const SELECTOR_STICKY_CONTENT = '.sticky-top'
const PROPERTY_PADDING = 'paddingRight'
const PROPERTY_MARGIN = 'marginRight'

/**
 * Class definition
 */

class ScrollBarHelper {
  constructor() {
    this._element = document.body
  }

  // Public
  getWidth() {
    // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
    const documentWidth = document.documentElement.clientWidth
    return Math.abs(window.innerWidth - documentWidth)
  }

  hide() {
    const width = this.getWidth()
    this._disableOverFlow()
    // give padding to element to balance the hidden scrollbar width
    this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
    // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
    this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
    this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)
  }

  reset() {
    this._resetElementAttributes(this._element, 'overflow')
    this._resetElementAttributes(this._element, PROPERTY_PADDING)
    this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)
    this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)
  }

  isOverflowing() {
    return this.getWidth() > 0
  }

  // Private
  _disableOverFlow() {
    this._saveInitialAttribute(this._element, 'overflow')
    this._element.style.overflow = 'hidden'
  }

  _setElementAttributes(selector, styleProp, callback) {
    const scrollbarWidth = this.getWidth()
    const manipulationCallBack = element => {
      if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
        return
      }

      this._saveInitialAttribute(element, styleProp)
      const calculatedValue = window.getComputedStyle(element)[styleProp]
      element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px`
    }

    this._applyManipulationCallback(selector, manipulationCallBack)
  }

  _saveInitialAttribute(element, styleProp) {
    const actualValue = element.style[styleProp]
    if (actualValue) {
      Manipulator.setDataAttribute(element, styleProp, actualValue)
    }
  }

  _resetElementAttributes(selector, styleProp) {
    const manipulationCallBack = element => {
      const value = Manipulator.getDataAttribute(element, styleProp)
      if (typeof value === 'undefined') {
        element.style.removeProperty(styleProp)
        return
      }

      Manipulator.removeDataAttribute(element, styleProp)
      element.style[styleProp] = value
    }

    this._applyManipulationCallback(selector, manipulationCallBack)
  }

  _applyManipulationCallback(selector, callBack) {
    if (isElement(selector)) {
      callBack(selector)
      return
    }

    for (const sel of SelectorEngine.find(selector, this._element)) {
      callBack(sel)
    }
  }
}

export default ScrollBarHelper
<commit_msg>Scrollbar - remove margin/padding properties properly (#35388)<commit_after>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.1.3): util/scrollBar.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import SelectorEngine from '../dom/selector-engine'
import Manipulator from '../dom/manipulator'
import { isElement } from './index'

/**
 * Constants
 */

const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
const SELECTOR_STICKY_CONTENT = '.sticky-top'
const PROPERTY_PADDING = 'padding-right'
const PROPERTY_MARGIN = 'margin-right'

/**
 * Class definition
 */

class ScrollBarHelper {
  constructor() {
    this._element = document.body
  }

  // Public
  getWidth() {
    // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
    const documentWidth = document.documentElement.clientWidth
    return Math.abs(window.innerWidth - documentWidth)
  }

  hide() {
    const width = this.getWidth()
    this._disableOverFlow()
    // give padding to element to balance the hidden scrollbar width
    this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
    // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
    this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
    this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)
  }

  reset() {
    this._resetElementAttributes(this._element, 'overflow')
    this._resetElementAttributes(this._element, PROPERTY_PADDING)
    this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)
    this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)
  }

  isOverflowing() {
    return this.getWidth() > 0
  }

  // Private
  _disableOverFlow() {
    this._saveInitialAttribute(this._element, 'overflow')
    this._element.style.overflow = 'hidden'
  }

  _setElementAttributes(selector, styleProp, callback) {
    const scrollbarWidth = this.getWidth()
    const manipulationCallBack = element => {
      if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
        return
      }

      this._saveInitialAttribute(element, styleProp)
      const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProp)
      element.style.setProperty(styleProp, `${callback(Number.parseFloat(calculatedValue))}px`)
    }

    this._applyManipulationCallback(selector, manipulationCallBack)
  }

  _saveInitialAttribute(element, styleProp) {
    const actualValue = element.style.getPropertyValue(styleProp)
    if (actualValue) {
      Manipulator.setDataAttribute(element, styleProp, actualValue)
    }
  }

  _resetElementAttributes(selector, styleProp) {
    const manipulationCallBack = element => {
      const value = Manipulator.getDataAttribute(element, styleProp)
      // We only want to remove the property if the value is `null`; the value can also be zero
      if (value === null) {
        element.style.removeProperty(styleProp)
        return
      }

      Manipulator.removeDataAttribute(element, styleProp)
      element.style.setProperty(styleProp, value)
    }

    this._applyManipulationCallback(selector, manipulationCallBack)
  }

  _applyManipulationCallback(selector, callBack) {
    if (isElement(selector)) {
      callBack(selector)
      return
    }

    for (const sel of SelectorEngine.find(selector, this._element)) {
      callBack(sel)
    }
  }
}

export default ScrollBarHelper
<|endoftext|>


<commit_before>$(function () {
  'use strict';

  module('popover plugin')

  test('should be defined on jquery object', function () {
    ok($(document.body).popover, 'popover method is defined')
  })

  module('popover', {
    setup: function () {
      // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
      $.fn.bootstrapPopover = $.fn.popover.noConflict()
    },
    teardown: function () {
      $.fn.popover = $.fn.bootstrapPopover
      delete $.fn.bootstrapPopover
    }
  })

  test('should provide no conflict', function () {
    strictEqual($.fn.popover, undefined, 'popover was set back to undefined (org value)')
  })

  test('should return jquery collection containing the element', function () {
    var $el = $('
')
    var $popover = $el.bootstrapPopover()
    ok($popover instanceof $, 'returns jquery collection')
    strictEqual($popover[0], $el[0], 'collection contains element')
  })

  test('should render popover element', function () {
    var $popover = $('@mdo')
      .appendTo('#qunit-fixture')
      .bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover removed')
  })

  test('should store popover instance in popover data object', function () {
    var $popover = $('@mdo').bootstrapPopover()

    ok($popover.data('bs.popover'), 'popover instance exists')
  })

  test('should store popover trigger in popover instance data object', function () {
    var $popover = $('@ResentedHook')
      .appendTo('#qunit-fixture')
      .bootstrapPopover()

    $popover.bootstrapPopover('show')

    ok($('.popover').data('bs.popover'), 'popover trigger stored in instance data')
  })

  test('should get title and content from options', function () {
    var $popover = $('@fat')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        title: function () {
          return '@fat'
        },
        content: function () {
          return 'loves writing tests （╯°□°）╯︵ ┻━┻'
        }
      })

    $popover.bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-title').text(), '@fat', 'title correctly inserted')
    equal($('.popover .popover-content').text(), 'loves writing tests （╯°□°）╯︵ ┻━┻', 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should not duplicate HTML object', function () {
    var $div = $('
').html('loves writing tests （╯°□°）╯︵ ┻━┻')

    var $popover = $('@fat')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        content: function () {
          return $div
        }
      })

    $popover.bootstrapPopover('show')
    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-content').html(), $div, 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')

    $popover.bootstrapPopover('show')
    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-content').html(), $div, 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should get title and content from attributes', function () {
    var $popover = $('@mdo')
      .appendTo('#qunit-fixture')
      .bootstrapPopover()
      .bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
    equal($('.popover .popover-content').text(), 'loves data attributes (づ｡◕‿‿◕｡)づ ︵ ┻━┻', 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should get title and content from attributes ignoring options passed via js', function () {
    var $popover = $('@mdo')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        title: 'ignored title option',
        content: 'ignored content option'
      })
      .bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
    equal($('.popover .popover-content').text(), 'loves data attributes (づ｡◕‿‿◕｡)づ ︵ ┻━┻', 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should respect custom template', function () {
    var $popover = $('@fat')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        title: 'Test',
        content: 'Test',
        template: '

'
      })

    $popover.bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    ok($('.popover').hasClass('foobar'), 'custom class is present')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should destroy popover', function () {
    var $popover = $('
')
      .bootstrapPopover({
        trigger: 'hover'
      })
      .on('click.foo', $.noop)

    ok($popover.data('bs.popover'), 'popover has data')
    ok($._data($popover[0], 'events').mouseover && $._data($popover[0], 'events').mouseout, 'popover has hover event')
    equal($._data($popover[0], 'events').click[0].namespace, 'foo', 'popover has extra click.foo event')

    $popover.bootstrapPopover('show')
    $popover.bootstrapPopover('destroy')

    ok(!$popover.hasClass('in'), 'popover is hidden')
    ok(!$popover.data('popover'), 'popover does not have data')
    equal($._data($popover[0], 'events').click[0].namespace, 'foo', 'popover still has click.foo')
    ok(!$._data($popover[0], 'events').mouseover && !$._data($popover[0], 'events').mouseout, 'popover does not have any events')
  })

  test('should render popover element using delegated selector', function () {
    var $div = $('
@mdo
')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        selector: 'a',
        trigger: 'click'
      })

    $div.find('a').click()
    notEqual($('.popover').length, 0, 'popover was inserted')

    $div.find('a').click()
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should render popover elements using different delegated selectors on the same node', function () {
    var popoverHTML = '
'
        + '@mdo'
        + '@mdo'
        + '
'

    var $div = $(popoverHTML)
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        selector: 'a.first',
        trigger: 'click'
      })
      .bootstrapPopover({
        selector: 'a.second',
        trigger: 'click'
      })

    $div.find('a.first').click()
    notEqual($('.popover').length, 0, 'first popover was inserted')

    $div.find('a.first').click()
    equal($('.popover').length, 0, 'first popover removed')

    $div.find('a.second').click()
    notEqual($('.popover').length, 0, 'second popover was inserted')

    $div.find('a.second').click()
    equal($('.popover').length, 0, 'second popover removed')
  })
})
<commit_msg>Add regression test for #14244.<commit_after>$(function () {
  'use strict';

  module('popover plugin')

  test('should be defined on jquery object', function () {
    ok($(document.body).popover, 'popover method is defined')
  })

  module('popover', {
    setup: function () {
      // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
      $.fn.bootstrapPopover = $.fn.popover.noConflict()
    },
    teardown: function () {
      $.fn.popover = $.fn.bootstrapPopover
      delete $.fn.bootstrapPopover
    }
  })

  test('should provide no conflict', function () {
    strictEqual($.fn.popover, undefined, 'popover was set back to undefined (org value)')
  })

  test('should return jquery collection containing the element', function () {
    var $el = $('
')
    var $popover = $el.bootstrapPopover()
    ok($popover instanceof $, 'returns jquery collection')
    strictEqual($popover[0], $el[0], 'collection contains element')
  })

  test('should render popover element', function () {
    var $popover = $('@mdo')
      .appendTo('#qunit-fixture')
      .bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover removed')
  })

  test('should store popover instance in popover data object', function () {
    var $popover = $('@mdo').bootstrapPopover()

    ok($popover.data('bs.popover'), 'popover instance exists')
  })

  test('should store popover trigger in popover instance data object', function () {
    var $popover = $('@ResentedHook')
      .appendTo('#qunit-fixture')
      .bootstrapPopover()

    $popover.bootstrapPopover('show')

    ok($('.popover').data('bs.popover'), 'popover trigger stored in instance data')
  })

  test('should get title and content from options', function () {
    var $popover = $('@fat')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        title: function () {
          return '@fat'
        },
        content: function () {
          return 'loves writing tests （╯°□°）╯︵ ┻━┻'
        }
      })

    $popover.bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-title').text(), '@fat', 'title correctly inserted')
    equal($('.popover .popover-content').text(), 'loves writing tests （╯°□°）╯︵ ┻━┻', 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should not duplicate HTML object', function () {
    var $div = $('
').html('loves writing tests （╯°□°）╯︵ ┻━┻')

    var $popover = $('@fat')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        content: function () {
          return $div
        }
      })

    $popover.bootstrapPopover('show')
    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-content').html(), $div, 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')

    $popover.bootstrapPopover('show')
    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-content').html(), $div, 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should get title and content from attributes', function () {
    var $popover = $('@mdo')
      .appendTo('#qunit-fixture')
      .bootstrapPopover()
      .bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
    equal($('.popover .popover-content').text(), 'loves data attributes (づ｡◕‿‿◕｡)づ ︵ ┻━┻', 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should get title and content from attributes ignoring options passed via js', function () {
    var $popover = $('@mdo')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        title: 'ignored title option',
        content: 'ignored content option'
      })
      .bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
    equal($('.popover .popover-content').text(), 'loves data attributes (づ｡◕‿‿◕｡)づ ︵ ┻━┻', 'content correctly inserted')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should respect custom template', function () {
    var $popover = $('@fat')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        title: 'Test',
        content: 'Test',
        template: '

'
      })

    $popover.bootstrapPopover('show')

    notEqual($('.popover').length, 0, 'popover was inserted')
    ok($('.popover').hasClass('foobar'), 'custom class is present')

    $popover.bootstrapPopover('hide')
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should destroy popover', function () {
    var $popover = $('
')
      .bootstrapPopover({
        trigger: 'hover'
      })
      .on('click.foo', $.noop)

    ok($popover.data('bs.popover'), 'popover has data')
    ok($._data($popover[0], 'events').mouseover && $._data($popover[0], 'events').mouseout, 'popover has hover event')
    equal($._data($popover[0], 'events').click[0].namespace, 'foo', 'popover has extra click.foo event')

    $popover.bootstrapPopover('show')
    $popover.bootstrapPopover('destroy')

    ok(!$popover.hasClass('in'), 'popover is hidden')
    ok(!$popover.data('popover'), 'popover does not have data')
    equal($._data($popover[0], 'events').click[0].namespace, 'foo', 'popover still has click.foo')
    ok(!$._data($popover[0], 'events').mouseover && !$._data($popover[0], 'events').mouseout, 'popover does not have any events')
  })

  test('should render popover element using delegated selector', function () {
    var $div = $('
@mdo
')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        selector: 'a',
        trigger: 'click'
      })

    $div.find('a').click()
    notEqual($('.popover').length, 0, 'popover was inserted')

    $div.find('a').click()
    equal($('.popover').length, 0, 'popover was removed')
  })

  test('should render popover elements using different delegated selectors on the same node', function () {
    var popoverHTML = '
'
        + '@mdo'
        + '@mdo'
        + '
'

    var $div = $(popoverHTML)
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        selector: 'a.first',
        trigger: 'click'
      })
      .bootstrapPopover({
        selector: 'a.second',
        trigger: 'click'
      })

    $div.find('a.first').click()
    notEqual($('.popover').length, 0, 'first popover was inserted')

    $div.find('a.first').click()
    equal($('.popover').length, 0, 'first popover removed')

    $div.find('a.second').click()
    notEqual($('.popover').length, 0, 'second popover was inserted')

    $div.find('a.second').click()
    equal($('.popover').length, 0, 'second popover removed')
  })

  test('should detach popover content rather than removing it so that event handlers are left intact', function () {
    var $content = $('
Button with event handler
').appendTo('#qunit-fixture')

    var handlerCalled = false;
    $('.content-with-handler .btn').click(function () {
      handlerCalled = true
    });

    var $div = $('
Show popover
')
      .appendTo('#qunit-fixture')
      .bootstrapPopover({
        html: true,
        trigger: 'manual',
        container: 'body',
        content: function () {
          return $content;
        }
      })

    stop()
    $div
      .one('shown.bs.popover', function () {
        $div
          .one('hidden.bs.popover', function () {
            $div
              .one('shown.bs.popover', function () {
                $('.content-with-handler .btn').click()
                $div.bootstrapPopover('destroy')
                ok(handlerCalled, 'content\'s event handler still present')
                start()
              })
              .bootstrapPopover('show')
          })
          .bootstrapPopover('hide')
      })
      .bootstrapPopover('show')
  })
})
<|endoftext|>