<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),
            '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) {
        let request = {
            'orderGuid': id,
        };
        const response = await this.privatePostGetOrderDetails (this.extend (request, params));
        return this.parseOrder (response);
    }

    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'];
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': undefined,
            'side': /Bid/.exec (trade['OrderType']) ? 'buy' : 'sell',
            '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 (),
            ];
            // remove this crap
            let keys = Object.keys (params);
            let payload = [];
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i];
                payload.push (key + '=' + params[key]);
            }
            auth = this.arrayConcat (auth, payload);
            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,
            });
            headers = { 'Content-Type': 'application/json' };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }
};
<commit_msg>independentreserve fixes #2410<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),
            '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 (),
            ];
            // remove this crap
            let keys = Object.keys (params);
            let payload = [];
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i];
                payload.push (key + '=' + params[key]);
            }
            auth = this.arrayConcat (auth, payload);
            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,
            });
            headers = { 'Content-Type': 'application/json' };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }
};
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Initializes phet globals that are used by all simulations, including assertions, phetioEvents and query-parameters.
 * See https://github.com/phetsims/phetcommon/issues/23
 *
 * PhET Simulations can be launched with query parameters which enable certain features.  To use a query parameter,
 * provide the full URL of the simulation and append a question mark (?) then the query parameter (and optionally its
 * value assignment).  For instance:
 * http://www.colorado.edu/physics/phet/dev/html/reactants-products-and-leftovers/1.0.0-dev.13/reactants-products-and-leftovers_en.html?dev
 *
 * Here is an example of a value assignment:
 * http://www.colorado.edu/physics/phet/dev/html/reactants-products-and-leftovers/1.0.0-dev.13/reactants-products-and-leftovers_en.html?webgl=false
 *
 * To use multiple query parameters, specify the question mark before the first query parameter, then ampersands (&)
 * between other query parameters.  Here is an example of multiple query parameters:
 * http://www.colorado.edu/physics/phet/dev/html/reactants-products-and-leftovers/1.0.0-dev.13/reactants-products-and-leftovers_en.html?dev&showPointerAreas&webgl=false
 *
 * For more on query parameters, please see http://en.wikipedia.org/wiki/Query_string
 *
 * Query parameters most useful for QA Testing:
 *
 * dev - enable developer-only features, such as showing the layout bounds
 * ea - enable assertions, internal code error checks
 * qrCode - adds a menu item that will open a window with a QR code with the URL of the simulation
 * fuzzMouse - randomly sends mouse events to sim
 * profiler - shows profiling information for the sim
 * showPointerAreas - touch areas in red, mouse areas in blue, both dotted outlines
 * webgl - can be set to false with ?webgl=false to turn off WebGL rendering, see https://github.com/phetsims/scenery/issues/289
 * stringTest - if set to "double", duplicates all of the translated strings which will allow to see (a) if all strings
 *              are translated and (b) whether the layout can accommodate longer strings from other languages.
 *              Note this is a heuristic rule that does not cover all cases.
 *            - if set to "long", then an exceptionally long string will be substituted for all strings
 *            - if set to "rtl", then a string that tests RTL (right-to-left) capabilities will be substituted for all strings
 *            - if set to "xss", it will test for security issues related to https://github.com/phetsims/special-ops/issues/18,
 *                  and running a sim should NOT redirect to another page. Preferably should be used for built versions or
 *                  other versions where assertions are not enabled (brackets can cause issues for SubSupText, etc.)
 *            - if set to "none" or omitted, then the normal translated string will be shown
 *            - if set to anything else, it will use that string everywhere.  This will allow testing specific cases, like
 *                  whether the word 'vitesse' would substitute for 'speed' well.  Also, using "/u20" it will show whitespace for all
 *                  of the strings, making it easy to identify non-translated strings
 *
 * Other query parameters:
 *
 * component - when running the scenery-phet example, select a particular component in the components screen
 * datamite.actionHistogram - when running with phetio.js + phetioEvents, display a histogram of the action types, see phetioEvents.jsadded action
 * accessibility - enable accessibility features, such as keyboard navigation (mileage may vary!)
 * eall - enable all assertions, as above but with more time consuming checks
 * rootRenderer - specify a renderer for the Sim's rootNode to use, such as 'svg', 'webgl' or 'canvas'
 * locale - test with a specific locale
 * playbackInputEventLog - plays event logging back from the server, provide an optional name for the session
 * recordInputEventLog - enables input event logging, provide an optional name for the session, log is available via PhET menu
 * sceneryLog - list of one or more logs to enable in scenery 0.2+, delimited with .
 *                          - For example: ?sceneryLog=Display.Drawable.WebGLBlock
 * sceneryStringLog - Scenery logs will be output to a string instead of the window
 * screens - select one or more screens (with a 1-based index) to run in the sim, with a dot instead of a comma delimiter.
 *                          - For example ?screens=3.1 will launch with screen 1 and 3 with 3 first and 1 second.
 *                          - ?screens=2 would launch with just screen 2.
 *                          - Note that launching with a subset of screens can speed up the startup time significantly
 *                          - because only the selected screens are initialized
 * showVisibleBounds - shows the visible bounds in ScreenView.js, for debugging the layout outside of the "dev" bounds
 * showHomeScreen - if false, go immediate to screenIndex, defaults to screenIndex=0
 * strings - override strings, value is JSON that is identical to string.json files
 * phet-io.log         - if set to 'console', will stream phetioEvents to console in JSON
 *                     - if set to 'lines', will stream colorized human-readable events to the console
 *                                        (only works for Chrome and Firefox)
 * phet-io.expressions - evaluate expressions on phet-io wrapper objects, like: ?phet-io.expressions=[["beaker.beakerScreen.soluteSelector","setVisible",[true]]]
 * phet-io.docs        - will output type documentation to the console, see https://github.com/phetsims/phet-io/issues/218
 * phet-io.standalone  - query parameter will cause a phet-io simulation to launch, even without a wrapper "go-ahead" step, see phet-io#181
 * phet-io.emitDeltas  - when running a simulation using phetio.js, outputs states and deltas within the phetioEvents data stream, see phetio.js
 * phet-io.emitEmptyDeltas - when emitting deltas using phetio.js (see phet-io.emitDeltas) emit deltas that are empty, to simplify playback in some systems like Metacog.
 * phet-io.emitInputEvents - emit the Scenery input events
 * phet-io.emitStates  - when running a simulation using phetio.js, outputs the state at the end of every frame
 * webglContextLossTimeout - if enabled, will create WebGL contexts that can simulate context loss
 *                         - if a value is specified, it will also simulate a context loss after the specified number
 *                         - of milliseconds has elapsed.
 *                         - The value can be omitted to manually simulate the context loss with simScene.simulateWebGLContextLoss()
 * webglContextLossIncremental - if this option is present, it will put the WebGLLayer into a testing mode which
 *                             - simulates context loss between successively increasing gl calls (starting at 1)
 *                             - this option should be used in conjunction with webglContextLossTimeout since
 *                             - it only triggers upon the first context loss.
 * buildCompatible - When present, will trigger changes that are more similar to the build environment. Right now, this
 *                   includes computing higher-resolution mipmaps for the mipmap plugin.
 * showCanvasNodeBounds - Displays an overlay of the current bounds of each CanvasNode
 * showFittedBlockBounds - Displays an overlay of the current bounds of each scenery.FittedBlock
 * showPointers - Displays a semi-transparent cursor indicator for the location of each active pointer on the screen.
 *
 * This file reads query parameters from browser window's URL.
 * This file must be loaded before requirejs is started up, and this file cannot be loaded as an AMD module.
 * The easiest way to do this is via a <script> tag in your HTML file.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Chris Malley (PixelZoom, Inc.)
 */
(function() {
  'use strict';

// Initialize query parameters, see docs above
  (function() {

    // Create the attachment point for all PhET globals
    window.phet = window.phet || {};
    window.phet.chipper = window.phet.chipper || {};

    // Create a random seed in the preload code that can be used to make sure playback simulations use the same seed
    // See Random.js
    // @public (writeable by phet-io) can be overwritten for replicable playback in phet-io.
    window.phet.chipper.randomSeed = Math.random();

    //Pre-populate the query parameters map so that multiple subsequent look-ups are fast
    var queryParamsMap = {};

    if ( typeof window !== 'undefined' && window.location.search ) {
      var params = window.location.search.slice( 1 ).split( '&' );
      for ( var i = 0; i < params.length; i++ ) {
        var nameValuePair = params[ i ].split( '=' );
        queryParamsMap[ nameValuePair[ 0 ] ] = decodeURIComponent( nameValuePair[ 1 ] );
      }
    }

    /**
     * Retrieves the first occurrence of a query parameter based on its key.
     * Returns undefined if the query parameter is not found.
     * @param {string} key
     * @return {string}
     */
    window.phet.chipper.getQueryParameter = function( key ) {
      return queryParamsMap[ key ];
    };

    /**
     * Retrieves the entire map of query parameters (may be empty)
     * @return {object} map from string->string
     */
    window.phet.chipper.getQueryParameters = function() {
      return queryParamsMap;
    };

    /**
     * Gets the cache buster args based on the provided query parameters.  Dy default it is:
     * ?bust=<number>
     * But this can be omitted if ?cacheBuster=false is provided
     * See https://github.com/phetsims/joist/issues/196
     * @returns {string}
     */
    window.phet.chipper.getCacheBusterArgs = function() {
      return (phet.chipper.getQueryParameter( 'cacheBuster' ) !== 'false') ? ('bust=' + Date.now()) : '';
    };

    /**
     * Gets the name of brand to use, which determines which logo to show in the navbar as well as what options
     * to show in the PhET menu and what text to show in the About dialog.
     * See https://github.com/phetsims/brand/issues/11
     * @returns {string}
     */
    window.phet.chipper.brand = window.phet.chipper.brand || phet.chipper.getQueryParameter( 'brand' ) || 'adapted-from-phet';

    /**
     * Maps an input string to a final string, accommodating tricks like doubleStrings.
     * This function is used to modify all strings in a sim when the stringTest query parameter is used.
     * The stringTest query parameter and its options are documented in the query parameter docs above.
     * It is used in string.js and sim.html.
     * @param string - the string to be mapped
     * @param stringTest - the value of the stringTest query parameter
     * @returns {*}
     */
    window.phet.chipper.mapString = function( string, stringTest ) {
      return stringTest === null ? string :
             stringTest === 'double' ? string + ':' + string :
             stringTest === 'long' ? '12345678901234567890123456789012345678901234567890' :
             stringTest === 'rtl' ? '\u202b\u062a\u0633\u062a (\u0632\u0628\u0627\u0646)\u202c' :
             stringTest === 'xss' ? '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NkYGD4DwABCQEBtxmN7wAAAABJRU5ErkJggg==" onload="window.location.href=atob(\'aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ==\')" />' :
             stringTest === 'none' ? string :

               //In the fallback case, supply whatever string was given in the query parameter value
             stringTest;
    };
  }());
  /**
   * Enables or disables assertions in common libraries using query parameters.
   * There are two types of assertions: basic and slow. Enabling slow assertions will adversely impact performance.
   * 'ea' enables basic assertions, 'eall' enables basic and slow assertions.
   * Must be run before RequireJS, and assumes that assert.js and query-parameters.js has been run.
   */
  (function() {

    // TODO: separate this logic out into a more common area?
    var isProduction = $( 'meta[name=phet-sim-level]' ).attr( 'content' ) === 'production';

    var enableAllAssertions = !isProduction && !!phet.chipper.getQueryParameter( 'eall' ); // enables all assertions (basic and slow)
    var enableBasicAssertions = enableAllAssertions || ( !isProduction && !!phet.chipper.getQueryParameter( 'ea' ) );  // enables basic assertions

    if ( enableBasicAssertions ) {
      window.assertions.enableAssert();
    }
    if ( enableAllAssertions ) {
      window.assertions.enableAssertSlow();
    }

    // Communicate sim errors to joist/tests/test-sims.html
    if ( phet.chipper.getQueryParameter( 'postMessageOnError' ) ) {
      window.addEventListener( 'error', function( a, b, c, d, e ) {
        var message = '';
        var stack = '';
        if ( a && a.message ) {
          message = a.message;
        }
        if ( a && a.error && a.error.stack ) {
          stack = a.error.stack;
        }
        window.parent && window.parent.postMessage( JSON.stringify( {
          type: 'error',
          url: window.location.href,
          message: message,
          stack: stack
        } ), '*' );
      } );
    }
  }());
}());
<commit_msg>Added a query parameter for game up integration, see https://github.com/phetsims/phet-io/issues/465<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Initializes phet globals that are used by all simulations, including assertions, phetioEvents and query-parameters.
 * See https://github.com/phetsims/phetcommon/issues/23
 *
 * PhET Simulations can be launched with query parameters which enable certain features.  To use a query parameter,
 * provide the full URL of the simulation and append a question mark (?) then the query parameter (and optionally its
 * value assignment).  For instance:
 * http://www.colorado.edu/physics/phet/dev/html/reactants-products-and-leftovers/1.0.0-dev.13/reactants-products-and-leftovers_en.html?dev
 *
 * Here is an example of a value assignment:
 * http://www.colorado.edu/physics/phet/dev/html/reactants-products-and-leftovers/1.0.0-dev.13/reactants-products-and-leftovers_en.html?webgl=false
 *
 * To use multiple query parameters, specify the question mark before the first query parameter, then ampersands (&)
 * between other query parameters.  Here is an example of multiple query parameters:
 * http://www.colorado.edu/physics/phet/dev/html/reactants-products-and-leftovers/1.0.0-dev.13/reactants-products-and-leftovers_en.html?dev&showPointerAreas&webgl=false
 *
 * For more on query parameters, please see http://en.wikipedia.org/wiki/Query_string
 *
 * Query parameters most useful for QA Testing:
 *
 * dev - enable developer-only features, such as showing the layout bounds
 * ea - enable assertions, internal code error checks
 * qrCode - adds a menu item that will open a window with a QR code with the URL of the simulation
 * fuzzMouse - randomly sends mouse events to sim
 * profiler - shows profiling information for the sim
 * showPointerAreas - touch areas in red, mouse areas in blue, both dotted outlines
 * webgl - can be set to false with ?webgl=false to turn off WebGL rendering, see https://github.com/phetsims/scenery/issues/289
 * stringTest - if set to "double", duplicates all of the translated strings which will allow to see (a) if all strings
 *              are translated and (b) whether the layout can accommodate longer strings from other languages.
 *              Note this is a heuristic rule that does not cover all cases.
 *            - if set to "long", then an exceptionally long string will be substituted for all strings
 *            - if set to "rtl", then a string that tests RTL (right-to-left) capabilities will be substituted for all strings
 *            - if set to "xss", it will test for security issues related to https://github.com/phetsims/special-ops/issues/18,
 *                  and running a sim should NOT redirect to another page. Preferably should be used for built versions or
 *                  other versions where assertions are not enabled (brackets can cause issues for SubSupText, etc.)
 *            - if set to "none" or omitted, then the normal translated string will be shown
 *            - if set to anything else, it will use that string everywhere.  This will allow testing specific cases, like
 *                  whether the word 'vitesse' would substitute for 'speed' well.  Also, using "/u20" it will show whitespace for all
 *                  of the strings, making it easy to identify non-translated strings
 *
 * Other query parameters:
 *
 * component - when running the scenery-phet example, select a particular component in the components screen
 * datamite.actionHistogram - when running with phetio.js + phetioEvents, display a histogram of the action types, see phetioEvents.jsadded action
 * accessibility - enable accessibility features, such as keyboard navigation (mileage may vary!)
 * eall - enable all assertions, as above but with more time consuming checks
 * gameUp - Launch the game-up-camera code which delivers images to requests in BrainPOP/Game Up/SnapThought
 * rootRenderer - specify a renderer for the Sim's rootNode to use, such as 'svg', 'webgl' or 'canvas'
 * locale - test with a specific locale
 * playbackInputEventLog - plays event logging back from the server, provide an optional name for the session
 * recordInputEventLog - enables input event logging, provide an optional name for the session, log is available via PhET menu
 * sceneryLog - list of one or more logs to enable in scenery 0.2+, delimited with .
 *                          - For example: ?sceneryLog=Display.Drawable.WebGLBlock
 * sceneryStringLog - Scenery logs will be output to a string instead of the window
 * screens - select one or more screens (with a 1-based index) to run in the sim, with a dot instead of a comma delimiter.
 *                          - For example ?screens=3.1 will launch with screen 1 and 3 with 3 first and 1 second.
 *                          - ?screens=2 would launch with just screen 2.
 *                          - Note that launching with a subset of screens can speed up the startup time significantly
 *                          - because only the selected screens are initialized
 * showVisibleBounds - shows the visible bounds in ScreenView.js, for debugging the layout outside of the "dev" bounds
 * showHomeScreen - if false, go immediate to screenIndex, defaults to screenIndex=0
 * strings - override strings, value is JSON that is identical to string.json files
 * phet-io.log         - if set to 'console', will stream phetioEvents to console in JSON
 *                     - if set to 'lines', will stream colorized human-readable events to the console
 *                                        (only works for Chrome and Firefox)
 * phet-io.expressions - evaluate expressions on phet-io wrapper objects, like: ?phet-io.expressions=[["beaker.beakerScreen.soluteSelector","setVisible",[true]]]
 * phet-io.docs        - will output type documentation to the console, see https://github.com/phetsims/phet-io/issues/218
 * phet-io.standalone  - query parameter will cause a phet-io simulation to launch, even without a wrapper "go-ahead" step, see phet-io#181
 * phet-io.emitDeltas  - when running a simulation using phetio.js, outputs states and deltas within the phetioEvents data stream, see phetio.js
 * phet-io.emitEmptyDeltas - when emitting deltas using phetio.js (see phet-io.emitDeltas) emit deltas that are empty, to simplify playback in some systems like Metacog.
 * phet-io.emitInputEvents - emit the Scenery input events
 * phet-io.emitStates  - when running a simulation using phetio.js, outputs the state at the end of every frame
 * webglContextLossTimeout - if enabled, will create WebGL contexts that can simulate context loss
 *                         - if a value is specified, it will also simulate a context loss after the specified number
 *                         - of milliseconds has elapsed.
 *                         - The value can be omitted to manually simulate the context loss with simScene.simulateWebGLContextLoss()
 * webglContextLossIncremental - if this option is present, it will put the WebGLLayer into a testing mode which
 *                             - simulates context loss between successively increasing gl calls (starting at 1)
 *                             - this option should be used in conjunction with webglContextLossTimeout since
 *                             - it only triggers upon the first context loss.
 * buildCompatible - When present, will trigger changes that are more similar to the build environment. Right now, this
 *                   includes computing higher-resolution mipmaps for the mipmap plugin.
 * showCanvasNodeBounds - Displays an overlay of the current bounds of each CanvasNode
 * showFittedBlockBounds - Displays an overlay of the current bounds of each scenery.FittedBlock
 * showPointers - Displays a semi-transparent cursor indicator for the location of each active pointer on the screen.
 *
 * This file reads query parameters from browser window's URL.
 * This file must be loaded before requirejs is started up, and this file cannot be loaded as an AMD module.
 * The easiest way to do this is via a <script> tag in your HTML file.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Chris Malley (PixelZoom, Inc.)
 */
(function() {
  'use strict';

// Initialize query parameters, see docs above
  (function() {

    // Create the attachment point for all PhET globals
    window.phet = window.phet || {};
    window.phet.chipper = window.phet.chipper || {};

    // Create a random seed in the preload code that can be used to make sure playback simulations use the same seed
    // See Random.js
    // @public (writeable by phet-io) can be overwritten for replicable playback in phet-io.
    window.phet.chipper.randomSeed = Math.random();

    //Pre-populate the query parameters map so that multiple subsequent look-ups are fast
    var queryParamsMap = {};

    if ( typeof window !== 'undefined' && window.location.search ) {
      var params = window.location.search.slice( 1 ).split( '&' );
      for ( var i = 0; i < params.length; i++ ) {
        var nameValuePair = params[ i ].split( '=' );
        queryParamsMap[ nameValuePair[ 0 ] ] = decodeURIComponent( nameValuePair[ 1 ] );
      }
    }

    /**
     * Retrieves the first occurrence of a query parameter based on its key.
     * Returns undefined if the query parameter is not found.
     * @param {string} key
     * @return {string}
     */
    window.phet.chipper.getQueryParameter = function( key ) {
      return queryParamsMap[ key ];
    };

    /**
     * Retrieves the entire map of query parameters (may be empty)
     * @return {object} map from string->string
     */
    window.phet.chipper.getQueryParameters = function() {
      return queryParamsMap;
    };

    /**
     * Gets the cache buster args based on the provided query parameters.  Dy default it is:
     * ?bust=<number>
     * But this can be omitted if ?cacheBuster=false is provided
     * See https://github.com/phetsims/joist/issues/196
     * @returns {string}
     */
    window.phet.chipper.getCacheBusterArgs = function() {
      return (phet.chipper.getQueryParameter( 'cacheBuster' ) !== 'false') ? ('bust=' + Date.now()) : '';
    };

    /**
     * Gets the name of brand to use, which determines which logo to show in the navbar as well as what options
     * to show in the PhET menu and what text to show in the About dialog.
     * See https://github.com/phetsims/brand/issues/11
     * @returns {string}
     */
    window.phet.chipper.brand = window.phet.chipper.brand || phet.chipper.getQueryParameter( 'brand' ) || 'adapted-from-phet';

    /**
     * Maps an input string to a final string, accommodating tricks like doubleStrings.
     * This function is used to modify all strings in a sim when the stringTest query parameter is used.
     * The stringTest query parameter and its options are documented in the query parameter docs above.
     * It is used in string.js and sim.html.
     * @param string - the string to be mapped
     * @param stringTest - the value of the stringTest query parameter
     * @returns {*}
     */
    window.phet.chipper.mapString = function( string, stringTest ) {
      return stringTest === null ? string :
             stringTest === 'double' ? string + ':' + string :
             stringTest === 'long' ? '12345678901234567890123456789012345678901234567890' :
             stringTest === 'rtl' ? '\u202b\u062a\u0633\u062a (\u0632\u0628\u0627\u0646)\u202c' :
             stringTest === 'xss' ? '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NkYGD4DwABCQEBtxmN7wAAAABJRU5ErkJggg==" onload="window.location.href=atob(\'aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ==\')" />' :
             stringTest === 'none' ? string :

               //In the fallback case, supply whatever string was given in the query parameter value
             stringTest;
    };
  }());
  /**
   * Enables or disables assertions in common libraries using query parameters.
   * There are two types of assertions: basic and slow. Enabling slow assertions will adversely impact performance.
   * 'ea' enables basic assertions, 'eall' enables basic and slow assertions.
   * Must be run before RequireJS, and assumes that assert.js and query-parameters.js has been run.
   */
  (function() {

    // TODO: separate this logic out into a more common area?
    var isProduction = $( 'meta[name=phet-sim-level]' ).attr( 'content' ) === 'production';

    var enableAllAssertions = !isProduction && !!phet.chipper.getQueryParameter( 'eall' ); // enables all assertions (basic and slow)
    var enableBasicAssertions = enableAllAssertions || ( !isProduction && !!phet.chipper.getQueryParameter( 'ea' ) );  // enables basic assertions

    if ( enableBasicAssertions ) {
      window.assertions.enableAssert();
    }
    if ( enableAllAssertions ) {
      window.assertions.enableAssertSlow();
    }

    // Communicate sim errors to joist/tests/test-sims.html
    if ( phet.chipper.getQueryParameter( 'postMessageOnError' ) ) {
      window.addEventListener( 'error', function( a, b, c, d, e ) {
        var message = '';
        var stack = '';
        if ( a && a.message ) {
          message = a.message;
        }
        if ( a && a.error && a.error.stack ) {
          stack = a.error.stack;
        }
        window.parent && window.parent.postMessage( JSON.stringify( {
          type: 'error',
          url: window.location.href,
          message: message,
          stack: stack
        } ), '*' );
      } );
    }
  }());
}());
<|endoftext|>


<commit_before>// Google Maps specific variable declaration
var geocoder;
var autocomplete;

// Variables for 'autocomplete' on the start and end location text boxes
var startLocation;
var destinationLocation;

// Map object storage variable
var map;

// Store the travel type
var travelType = "driving";

/* FOR POPUP PAGE */
// Variables of the current location set by asynchronous method
var currentLocationName;
var currentLocationLat;
var currentLocationLng;
var addedAttractionsArray = new Array();

function initialize() {
	//Map Specifications
	var mapProp = {
		center:new google.maps.LatLng(-24.994167,134.866944),
		zoom:5,
		mapTypeId:google.maps.MapTypeId.ROADMAP
	};

	//Add Map to Div
	map=new google.maps.Map(document.getElementById("map-canvas"),mapProp);

	//Initialise Geocoder object
	geocoder = new google.maps.Geocoder();

	// Initialising the autocomplete objects, restricting the search
	// to geographical location types.
	startLocationAutocomplete = new google.maps.places.Autocomplete((document.getElementById('start-location')),{ types: ['geocode'] });
	destinationLocationAutocomplete = new google.maps.places.Autocomplete((document.getElementById('end-location')),{ types: ['geocode'] });

	// When the user selects an address from the dropdown,
	// populate the address fields in the form.
	// start location autocomplete event
	google.maps.event.addListener(startLocationAutocomplete, 'place_changed', function() {
		geocodeAddress(document.getElementById('start-location').value, 1);
	});

	// destination location autocomplete event
	google.maps.event.addListener(destinationLocationAutocomplete, 'place_changed', function() {
		geocodeAddress(document.getElementById('end-location').value, 2);
	});

	//------------------------------------------------------------------------------------------------------------------------
	//POPUPWINDOW
	//------------------------------------------------------------------------------------------------------------------------

	//POPUPWINDOW: Initialise date picker for departure time
	if (!Modernizr.inputtypes.datetime || !!window.chrome) {
		$("#departure-time").datetimepicker();
	} else {
		// Remove the button and place the input field in the parent
		// CSS breaks if the button is removed without reordering DOM
		$("#departure-time>span").remove();
		$("#departure-time>input").attr("type", "date");
		$("#departure-time>input").appendTo($("#departure-time").parent());
	}

	//POPUPWINDOW: Autocomplete search filtering conditions set to only establishments within australia
	var options = {
		types: ['establishment'],
		componentRestrictions: {country: "aus"}
	};

	//POPUPWINDOW: Textfield input
	var input = document.getElementById('attraction-location');

	// Initialising the autocomplete objects, restricting the search
	// to geographical location types.
	startLocationAutocomplete = new google.maps.places.Autocomplete(input,options);

	// When the user selects an address from the dropdown,
	// populate the address fields in the form.
	//start location autocomplete event
	google.maps.event.addListener(startLocationAutocomplete, 'place_changed', function() {

		currentLocationName = document.getElementById('attraction-location').value;
		geocodeAddress(document.getElementById('attraction-location').value, 3);
	});

}

//Takes the entered address and set the start and end location latitude and longitude
//The assign variable being a 1 or a 2 determines whether the locations is the start or destination
function geocodeAddress(location, assign) {
	geocoder.geocode( { 'address': location}, function(results, status) {
		if (status == google.maps.GeocoderStatus.OK) {
			//global variable assignment
			if(assign == 1){
				startLocation = results[0].geometry.location;
			} else if(assign == 2){
				destinationLocation = results[0].geometry.location;
			} else if(assign == 3){
			currentLocationLat = results[0].geometry.location.lat();
			currentLocationLng = results[0].geometry.location.lng();

			}

		} else {
			alert('Geocode was not successful for the following reason: ' + status);
		}
	});
}

function addAttraction()
{
	var location = {name:currentLocationName, lat:currentLocationLat, lng:currentLocationLng};
	addedAttractionsArray.push(location);

	//Clears the attractions auto complete box
	document.getElementById("attraction-location").value = '';

	generateTable();
}

function generateTable()
{
	var table = document.getElementById("myTable");

	//DELETE ENTIRE TABLE - Very crude deletion method
	table.innerHTML = "";

	for (i = 0; i < addedAttractionsArray.length; i++)
	{
		var row = table.insertRow(-1);

		var locationNameCell = row.insertCell(0);
		var cell2 = row.insertCell(1);
		var cell3 = row.insertCell(2);

		locationNameCell.innerHTML = addedAttractionsArray[i].name;
		cell2.innerHTML = "[Time/ Distance this destination adds to the trip]";
		cell3.innerHTML = "<input type='checkbox'>";
	}
}

//(!BUG!) WITHIN METHOD NOT ALOT SELECTED ATTRACTIONS ARE DELETED
function deleteAttraction()
{
	var table=document.getElementById("myTable");
	var rowCount=table.rows.length;

	for(var i=0;i<rowCount;i++)
	{
		var chkbox = document.getElementById("myTable").rows[i].cells[2].childNodes[0];

		if(null!=chkbox&&true==chkbox.checked)
		{
			//Deletes an array entry - (Splice Method Supported by all browsers???)
			addedAttractionsArray.splice(i, 1)
		}

	}
	generateTable();
}

function calculateRoute(){
	//alert("Start Location: " + startLocation + "\nEnd Location: " + destinationLocation);

	console.log("Is " + travelType);

	if(travelType == "driving")
	{

	}
	else if(strUser == "walking")
	{

	}
	else if(strUser == "cycling")
	{

	}
	else if(strUser == "transit")
	{

	}
}

function setTravelType(originElement, newTravelType) {
	travelType = newTravelType;
	$("#travel-mode>.btn-group").each(function() {
		$(this).children().first().attr("class", "btn btn-default");
	});
	$(originElement).attr("class", "btn btn-success");
}

$(document).on('click touchend', '#select-attractions-button', function(){
	$('#overlay').fadeIn(500, function() {
		$('#popup').slideDown();
	});
});
$(document).on('click touchend', '#close-popup', function(){
	$('#popup').slideUp(500, function() {
		$('#overlay').fadeOut();
	});
});<commit_msg>i need a test suite for ios<commit_after>// Google Maps specific variable declaration
var geocoder;
var autocomplete;

// Variables for 'autocomplete' on the start and end location text boxes
var startLocation;
var destinationLocation;

// Map object storage variable
var map;

// Store the travel type
var travelType = "driving";

/* FOR POPUP PAGE */
// Variables of the current location set by asynchronous method
var currentLocationName;
var currentLocationLat;
var currentLocationLng;
var addedAttractionsArray = new Array();

function initialize() {
	//Map Specifications
	var mapProp = {
		center:new google.maps.LatLng(-24.994167,134.866944),
		zoom:5,
		mapTypeId:google.maps.MapTypeId.ROADMAP
	};

	//Add Map to Div
	map=new google.maps.Map(document.getElementById("map-canvas"),mapProp);

	//Initialise Geocoder object
	geocoder = new google.maps.Geocoder();

	// Initialising the autocomplete objects, restricting the search
	// to geographical location types.
	startLocationAutocomplete = new google.maps.places.Autocomplete((document.getElementById('start-location')),{ types: ['geocode'] });
	destinationLocationAutocomplete = new google.maps.places.Autocomplete((document.getElementById('end-location')),{ types: ['geocode'] });

	// When the user selects an address from the dropdown,
	// populate the address fields in the form.
	// start location autocomplete event
	google.maps.event.addListener(startLocationAutocomplete, 'place_changed', function() {
		geocodeAddress(document.getElementById('start-location').value, 1);
	});

	// destination location autocomplete event
	google.maps.event.addListener(destinationLocationAutocomplete, 'place_changed', function() {
		geocodeAddress(document.getElementById('end-location').value, 2);
	});

	//------------------------------------------------------------------------------------------------------------------------
	//POPUPWINDOW
	//------------------------------------------------------------------------------------------------------------------------

	//POPUPWINDOW: Initialise date picker for departure time
	if (!Modernizr.inputtypes.datetime || navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
		$("#departure-time").datetimepicker();
	} else {
		// Remove the button and place the input field in the parent
		// CSS breaks if the button is removed without reordering DOM
		$("#departure-time>span").remove();
		$("#departure-time>input").attr("type", "date");
		$("#departure-time>input").appendTo($("#departure-time").parent());
	}

	//POPUPWINDOW: Autocomplete search filtering conditions set to only establishments within australia
	var options = {
		types: ['establishment'],
		componentRestrictions: {country: "aus"}
	};

	//POPUPWINDOW: Textfield input
	var input = document.getElementById('attraction-location');

	// Initialising the autocomplete objects, restricting the search
	// to geographical location types.
	startLocationAutocomplete = new google.maps.places.Autocomplete(input,options);

	// When the user selects an address from the dropdown,
	// populate the address fields in the form.
	//start location autocomplete event
	google.maps.event.addListener(startLocationAutocomplete, 'place_changed', function() {

		currentLocationName = document.getElementById('attraction-location').value;
		geocodeAddress(document.getElementById('attraction-location').value, 3);
	});

}

//Takes the entered address and set the start and end location latitude and longitude
//The assign variable being a 1 or a 2 determines whether the locations is the start or destination
function geocodeAddress(location, assign) {
	geocoder.geocode( { 'address': location}, function(results, status) {
		if (status == google.maps.GeocoderStatus.OK) {
			//global variable assignment
			if(assign == 1){
				startLocation = results[0].geometry.location;
			} else if(assign == 2){
				destinationLocation = results[0].geometry.location;
			} else if(assign == 3){
			currentLocationLat = results[0].geometry.location.lat();
			currentLocationLng = results[0].geometry.location.lng();

			}

		} else {
			alert('Geocode was not successful for the following reason: ' + status);
		}
	});
}

function addAttraction()
{
	var location = {name:currentLocationName, lat:currentLocationLat, lng:currentLocationLng};
	addedAttractionsArray.push(location);

	//Clears the attractions auto complete box
	document.getElementById("attraction-location").value = '';

	generateTable();
}

function generateTable()
{
	var table = document.getElementById("myTable");

	//DELETE ENTIRE TABLE - Very crude deletion method
	table.innerHTML = "";

	for (i = 0; i < addedAttractionsArray.length; i++)
	{
		var row = table.insertRow(-1);

		var locationNameCell = row.insertCell(0);
		var cell2 = row.insertCell(1);
		var cell3 = row.insertCell(2);

		locationNameCell.innerHTML = addedAttractionsArray[i].name;
		cell2.innerHTML = "[Time/ Distance this destination adds to the trip]";
		cell3.innerHTML = "<input type='checkbox'>";
	}
}

//(!BUG!) WITHIN METHOD NOT ALOT SELECTED ATTRACTIONS ARE DELETED
function deleteAttraction()
{
	var table=document.getElementById("myTable");
	var rowCount=table.rows.length;

	for(var i=0;i<rowCount;i++)
	{
		var chkbox = document.getElementById("myTable").rows[i].cells[2].childNodes[0];

		if(null!=chkbox&&true==chkbox.checked)
		{
			//Deletes an array entry - (Splice Method Supported by all browsers???)
			addedAttractionsArray.splice(i, 1)
		}

	}
	generateTable();
}

function calculateRoute(){
	//alert("Start Location: " + startLocation + "\nEnd Location: " + destinationLocation);

	console.log("Is " + travelType);

	if(travelType == "driving")
	{

	}
	else if(strUser == "walking")
	{

	}
	else if(strUser == "cycling")
	{

	}
	else if(strUser == "transit")
	{

	}
}

function setTravelType(originElement, newTravelType) {
	travelType = newTravelType;
	$("#travel-mode>.btn-group").each(function() {
		$(this).children().first().attr("class", "btn btn-default");
	});
	$(originElement).attr("class", "btn btn-success");
}

$(document).on('click touchend', '#select-attractions-button', function(){
	$('#overlay').fadeIn(500, function() {
		$('#popup').slideDown();
	});
});
$(document).on('click touchend', '#close-popup', function(){
	$('#popup').slideUp(500, function() {
		$('#overlay').fadeOut();
	});
});<|endoftext|>


<commit_before>var async = require('async'),
  hat = require('hat'),
  rack = hat.rack(),
  LocalStrategy = require('passport-local').Strategy,
  HashStrategy = require('passport-hash').Strategy,
  GoogleStrategy = require('passport-google').Strategy,
  GitHubStrategy = require('passport-github').Strategy,
  TwitterStrategy = require('passport-twitter').Strategy,
  FacebookStrategy = require('passport-facebook').Strategy,
  LinkedInStrategy = require('passport-linkedin').Strategy;

exports.doInitializePassportStrategies = function (passport, Users, config) {

  function processProfile(profile, done) {
    var email = profile.emails[0].value;
    if (email) {
      console.log('==============');
      console.log(profile);
      console.log('==============');
      Users.findOne({'email': email}, function (err, userFound) {
        if (err) {
          return done(err, false, {'message': 'Database broken...'});
        } else {
          console.log('==============');
          console.log(userFound);
          console.log('==============');
          if (err) {
            return done(err);
          } else {
            if (userFound) {
              return done(err, userFound, {message: 'Welcome, ' + userFound.username});
            } else {
              Users.signUpByEmailOnly(email, function (err1, userCreated) {
                return done(err1, userCreated, { message: 'Please, complete your account!' });
              });
            }
          }
        }
      });
    } else {
      return done(new Error('There is something strange instead of user profile'));
    }
  };

  //initializing passport strategies
  passport.use(new LocalStrategy(
    function (username, password, done) {
      Users.findOne({username: username}, function (err, user) {
        if (err) {
          return done(err, false, {'message': 'Database broken...'});
        } else {
          if (user) {
            if (user.verifyPassword(password)) {
              return done(null, user, {message: 'Welcome, ' + user.username});
            } else {
              return done(null, false, { message: 'Access denied!' });
            }
          } else {
            return done(null, false, { message: 'Access denied!' });
          }
        }
      });
    }
  ));

  //used for verify account by email
  passport.use(new HashStrategy(function(hash,done){
    Users.findOneByApiKeyAndVerify(hash,function(err,userFound){
      done(err,userFound);
    });
  }));

  passport.use(new GoogleStrategy({
      returnURL: config.hostUrl + 'auth/google/return',
      realm: config.hostUrl
    },
    function (identifier, profile, done) {
      return processProfile(profile,done);
    }
  ));

  if (config.passport && config.passport.GITHUB_CLIENT_ID && config.passport.GITHUB_CLIENT_SECRET) {
    passport.use(new GitHubStrategy({
      clientID: config.passport.GITHUB_CLIENT_ID,
      clientSecret: config.passport.GITHUB_CLIENT_SECRET,
      callbackURL: config.hostUrl + 'auth/github/callback'
    }, function (accessToken, refreshToken, profile, done) {
      return processProfile(profile,done);
    }));
  }

  if (config.passport && config.passport.TWITTER_CONSUMER_KEY && config.passport.TWITTER_CONSUMER_SECRET) {
    passport.use(new TwitterStrategy({
      consumerKey: config.passport.TWITTER_CONSUMER_KEY,
      consumerSecret: config.passport.TWITTER_CONSUMER_SECRET,
      callbackURL: config.hostUrl + 'auth/twitter/callback'
    }, function (token, tokenSecret, profile, done) {
      return processProfile(profile,done);
    }));
  }

  if (config.passport && config.passport.FACEBOOK_APP_ID && config.passport.FACEBOOK_APP_SECRET) {
    passport.use(new FacebookStrategy({
        clientID: config.passport.FACEBOOK_APP_ID,
        clientSecret: config.passport.FACEBOOK_APP_SECRET,
        callbackURL: config.hostUrl + 'auth/facebook/callback'
      },
      function(accessToken, refreshToken, profile, done) {
        return processProfile(profile,done);
      }));
  }

  if (config.passport && config.passport.LINKEDIN_API_KEY && config.passport.LINKEDIN_SECRET_KEY) {
    passport.use(new LinkedInStrategy({
        consumerKey: config.passport.LINKEDIN_API_KEY,
        consumerSecret: config.passport.LINKEDIN_SECRET_KEY,
        callbackURL: config.hostUrl + 'auth/linkedin/callback'
      },
      function (token, tokenSecret, profile, done) {
        return processProfile(profile, done);
      }
    ));
  }
  //end of initializing passport strategies

  //Storing user in session, storage key is username
  passport.serializeUser(function (user, done) {
    done(null, user.username);
  });

  passport.deserializeUser(function (username, done) {
    Users.findOne({username: username}, function (err, user) {
      done(err, user);
    });
  });
  //end of setting passport
};

exports.doInitializePassportRoutes = function (passport, app, config) {

  //registration by username, password and email
  //todo - implement the https://npmjs.org/packages/captcha
  app.post('/auth/signup', function (request, response) {
    request.MODEL.Users.signUp(request.body.username, request.body.email, request.body.password,
      function (err, userCreated) {
        if (err) {
          request.flash('error', err.message);
        } else {
          userCreated.notify({'type': 'email', 'subject': "Account confirmation", 'message': {'template': 'signin'}});
          request.flash('info', 'You have been registered! Please, check your email for instructions!');
        }
        response.redirect('/');
      }
    );
  });

  //verify that login/email is not busy! - to be called by ajax
  app.get('/auth/isBusy',function(request,response){
    async.parallel({
      'username_ok':function(cb){
        var username=request.query.username;
        if(username){
          if(/^[a-zA-Z0-9_]{3,32}$/.test(username)){ //username looks OK
            request.MODEL.Users.findOne({'username':username},function(err,userFound){
              cb(err, (userFound?true:false));
            });
          } else {
            cb(null,false);//username looks bad
          }
        } else {
          cb(null,false);//username is empty
        }
      },
      'email_ok':function(cb){
        var email=request.query.email;
        if(email){
          // https://github.com/chriso/node-validator/blob/master/lib/validators.js#L27
          if(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/.test(email)){ //email looks OK
            request.MODEL.Users.findOne({'email':email},function(err,userFound){
              cb(err, userFound?true:false);
            });
          } else {
            cb(null,false);//email looks bad
          }
        } else {
          cb(null,false);//email is empty
        }
      }
    },function(err,results){
      if(err) throw err;
      response.json(results)
    });
  });

  //route to complete profile
  app.post('/auth/completeProfile',function(request,response){
    if(request.user){
      request.user.completeProfile(request.body.username,request.body.password,function(err){
        if(err){
          request.flash('error',err.message);
        } else {
          request.flash('success','Thanks! Your profile is completed!')
        }
        response.redirect('back');
      });
    } else {
      response.send(403);
    }
  });

  app.post('/auth/resetPassword',function(request,response){
    if(request.body.apiKey && request.body.password){
      request.MODEL.Users.findOneByApiKeyAndResetPassword(request.body.apiKey, request.body.password, function(err){
        if(err){
          request.flash('error',err.message);
        } else {
          request.flash('success','Thanks! Your password is reset!')
        }
        response.redirect('/');
      });
    } else {
      response.send(400, 'Wrong request! apiKey and password are missed!');
    }

  });

  //google works alwayes, it do not need tunning
  app.get('/auth/google', passport.authenticate('google'));
  app.get('/auth/google/return', passport.authenticate('google', { failureRedirect: '/', successRedirect: '/' }));

  //autorization by password and username
  app.post('/auth/login',passport.authenticate('local',{successRedirect: '/',failureRedirect: '/',failureFlash: true}));

  if (config.passport && config.passport.GITHUB_CLIENT_ID && config.passport.GITHUB_CLIENT_SECRET) {
    //if we have set parameters for github, enable the github passport strategy
    app.get('/auth/github', passport.authenticate('github'),
      function (req, res) {
        // The request will be redirected to GitHub for authentication, so this
        // function will not be called.
      });
    app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/' }),
      function (req, res) {
        res.redirect('/');
      });
  }

  if (config.passport && config.passport.TWITTER_CONSUMER_KEY && config.passport.TWITTER_CONSUMER_SECRET) {
    app.get('/auth/twitter', passport.authenticate('twitter'));
    app.get('/auth/twitter/callback', passport.authenticate('twitter', { successRedirect: '/', failureRedirect: '/' }));
  }

  if (config.passport && config.passport.FACEBOOK_APP_ID && config.passport.FACEBOOK_APP_SECRET) {
    app.get('/auth/facebook', passport.authenticate('facebook'));
    app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/', failureRedirect: '/' }));
  }

  if (config.passport && config.passport.LINKEDIN_API_KEY && config.passport.LINKEDIN_SECRET_KEY) {
    app.get('/auth/linkedin',passport.authenticate('linkedin'),function(req, res){});
    app.get('/auth/linkedin/callback',passport.authenticate('linkedin', { failureRedirect: '/' }),
      function(req, res) {
        res.redirect('/');
      });
  }

  //acount confirmation by link in email
  app.get('/confirm/:hash',
    passport.authenticate('hash', { failureRedirect: '/' }),
    function(req, res) {
      res.redirect('/');
    });

  app.post('/auth/logout', function (request, response) {
    request.logout();
    response.send(200, 'OK');
  });

}<commit_msg>[fix] passport - some fixes for email notification via mwc_plugin_welcome<commit_after>var async = require('async'),
  hat = require('hat'),
  rack = hat.rack(),
  LocalStrategy = require('passport-local').Strategy,
  HashStrategy = require('passport-hash').Strategy,
  GoogleStrategy = require('passport-google').Strategy,
  GitHubStrategy = require('passport-github').Strategy,
  TwitterStrategy = require('passport-twitter').Strategy,
  FacebookStrategy = require('passport-facebook').Strategy,
  LinkedInStrategy = require('passport-linkedin').Strategy;

exports.doInitializePassportStrategies = function (passport, Users, config) {

  function processProfile(profile, done) {
    var email = profile.emails[0].value;
    if (email) {
      console.log('==============');
      console.log(profile);
      console.log('==============');
      Users.findOne({'email': email}, function (err, userFound) {
        if (err) {
          return done(err, false, {'message': 'Database broken...'});
        } else {
          console.log('==============');
          console.log(userFound);
          console.log('==============');
          if (err) {
            return done(err);
          } else {
            if (userFound) {
              return done(err, userFound, {message: 'Welcome, ' + userFound.username});
            } else {
              Users.signUpByEmailOnly(email, function (err1, userCreated) {
                return done(err1, userCreated, { message: 'Please, complete your account!' });
              });
            }
          }
        }
      });
    } else {
      return done(new Error('There is something strange instead of user profile'));
    }
  };

  //initializing passport strategies
  passport.use(new LocalStrategy(
    function (username, password, done) {
      Users.findOne({username: username}, function (err, user) {
        if (err) {
          return done(err, false, {'message': 'Database broken...'});
        } else {
          if (user) {
            if (user.verifyPassword(password)) {
              return done(null, user, {message: 'Welcome, ' + user.username});
            } else {
              return done(null, false, { message: 'Access denied!' });
            }
          } else {
            return done(null, false, { message: 'Access denied!' });
          }
        }
      });
    }
  ));

  //used for verify account by email
  passport.use(new HashStrategy(function(hash,done){
    Users.findOneByApiKeyAndVerify(hash,function(err,userFound){
      done(err,userFound);
    });
  }));

  passport.use(new GoogleStrategy({
      returnURL: config.hostUrl + 'auth/google/return',
      realm: config.hostUrl
    },
    function (identifier, profile, done) {
      return processProfile(profile,done);
    }
  ));

  if (config.passport && config.passport.GITHUB_CLIENT_ID && config.passport.GITHUB_CLIENT_SECRET) {
    passport.use(new GitHubStrategy({
      clientID: config.passport.GITHUB_CLIENT_ID,
      clientSecret: config.passport.GITHUB_CLIENT_SECRET,
      callbackURL: config.hostUrl + 'auth/github/callback'
    }, function (accessToken, refreshToken, profile, done) {
      return processProfile(profile,done);
    }));
  }

  if (config.passport && config.passport.TWITTER_CONSUMER_KEY && config.passport.TWITTER_CONSUMER_SECRET) {
    passport.use(new TwitterStrategy({
      consumerKey: config.passport.TWITTER_CONSUMER_KEY,
      consumerSecret: config.passport.TWITTER_CONSUMER_SECRET,
      callbackURL: config.hostUrl + 'auth/twitter/callback'
    }, function (token, tokenSecret, profile, done) {
      return processProfile(profile,done);
    }));
  }

  if (config.passport && config.passport.FACEBOOK_APP_ID && config.passport.FACEBOOK_APP_SECRET) {
    passport.use(new FacebookStrategy({
        clientID: config.passport.FACEBOOK_APP_ID,
        clientSecret: config.passport.FACEBOOK_APP_SECRET,
        callbackURL: config.hostUrl + 'auth/facebook/callback'
      },
      function(accessToken, refreshToken, profile, done) {
        return processProfile(profile,done);
      }));
  }

  if (config.passport && config.passport.LINKEDIN_API_KEY && config.passport.LINKEDIN_SECRET_KEY) {
    passport.use(new LinkedInStrategy({
        consumerKey: config.passport.LINKEDIN_API_KEY,
        consumerSecret: config.passport.LINKEDIN_SECRET_KEY,
        callbackURL: config.hostUrl + 'auth/linkedin/callback'
      },
      function (token, tokenSecret, profile, done) {
        return processProfile(profile, done);
      }
    ));
  }
  //end of initializing passport strategies

  //Storing user in session, storage key is username
  passport.serializeUser(function (user, done) {
    done(null, user.username);
  });

  passport.deserializeUser(function (username, done) {
    Users.findOne({username: username}, function (err, user) {
      done(err, user);
    });
  });
  //end of setting passport
};

exports.doInitializePassportRoutes = function (passport, app, config) {

  //registration by username, password and email
  //todo - implement the https://npmjs.org/packages/captcha
  app.post('/auth/signup', function (request, response) {
    request.MODEL.Users.signUp(request.body.username, request.body.email, request.body.password,
      function (err, userCreated) {
        if (err) {
          request.flash('error', err.message);
        } else {
          userCreated.notify({'type':'email','subject': "Account confirmation", 'template': 'signin'});
          request.flash('info', 'You have been registered! Please, check your email for instructions!');
        }
        response.redirect('/');
      }
    );
  });

  //verify that login/email is not busy! - to be called by ajax
  app.get('/auth/isBusy',function(request,response){
    async.parallel({
      'username_ok':function(cb){
        var username=request.query.username;
        if(username){
          if(/^[a-zA-Z0-9_]{3,32}$/.test(username)){ //username looks OK
            request.MODEL.Users.findOne({'username':username},function(err,userFound){
              cb(err, (userFound?true:false));
            });
          } else {
            cb(null,false);//username looks bad
          }
        } else {
          cb(null,false);//username is empty
        }
      },
      'email_ok':function(cb){
        var email=request.query.email;
        if(email){
          // https://github.com/chriso/node-validator/blob/master/lib/validators.js#L27
          if(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/.test(email)){ //email looks OK
            request.MODEL.Users.findOne({'email':email},function(err,userFound){
              cb(err, userFound?true:false);
            });
          } else {
            cb(null,false);//email looks bad
          }
        } else {
          cb(null,false);//email is empty
        }
      }
    },function(err,results){
      if(err) throw err;
      response.json(results)
    });
  });

  //route to complete profile
  app.post('/auth/completeProfile',function(request,response){
    if(request.user){
      request.user.completeProfile(request.body.username,request.body.password,function(err){
        if(err){
          request.flash('error',err.message);
        } else {
          request.flash('success','Thanks! Your profile is completed!')
        }
        response.redirect('back');
      });
    } else {
      response.send(403);
    }
  });

  app.post('/auth/resetPassword',function(request,response){
    if(request.body.apiKey && request.body.password){
      request.MODEL.Users.findOneByApiKeyAndResetPassword(request.body.apiKey, request.body.password, function(err){
        if(err){
          request.flash('error',err.message);
        } else {
          request.flash('success','Thanks! Your password is reset!')
        }
        response.redirect('/');
      });
    } else {
      response.send(400, 'Wrong request! apiKey and password are missed!');
    }

  });

  //google works alwayes, it do not need tunning
  app.get('/auth/google', passport.authenticate('google'));
  app.get('/auth/google/return', passport.authenticate('google', { failureRedirect: '/', successRedirect: '/' }));

  //autorization by password and username
  app.post('/auth/login',passport.authenticate('local',{successRedirect: '/',failureRedirect: '/',failureFlash: true}));

  if (config.passport && config.passport.GITHUB_CLIENT_ID && config.passport.GITHUB_CLIENT_SECRET) {
    //if we have set parameters for github, enable the github passport strategy
    app.get('/auth/github', passport.authenticate('github'),
      function (req, res) {
        // The request will be redirected to GitHub for authentication, so this
        // function will not be called.
      });
    app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/' }),
      function (req, res) {
        res.redirect('/');
      });
  }

  if (config.passport && config.passport.TWITTER_CONSUMER_KEY && config.passport.TWITTER_CONSUMER_SECRET) {
    app.get('/auth/twitter', passport.authenticate('twitter'));
    app.get('/auth/twitter/callback', passport.authenticate('twitter', { successRedirect: '/', failureRedirect: '/' }));
  }

  if (config.passport && config.passport.FACEBOOK_APP_ID && config.passport.FACEBOOK_APP_SECRET) {
    app.get('/auth/facebook', passport.authenticate('facebook'));
    app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/', failureRedirect: '/' }));
  }

  if (config.passport && config.passport.LINKEDIN_API_KEY && config.passport.LINKEDIN_SECRET_KEY) {
    app.get('/auth/linkedin',passport.authenticate('linkedin'),function(req, res){});
    app.get('/auth/linkedin/callback',passport.authenticate('linkedin', { failureRedirect: '/' }),
      function(req, res) {
        res.redirect('/');
      });
  }

  //acount confirmation by link in email
  app.get('/auth/confirm/:hash',
    passport.authenticate('hash', { failureRedirect: '/' }),
    function(req, res) {
      res.redirect('/');
    });

  app.post('/auth/logout', function (request, response) {
    request.logout();
    response.send(200, 'OK');
  });

}<|endoftext|>


<commit_before>"use strict";

var Core = require("lapis-core/index.js");
var Data = require("lazuli-data/index.js");

module.exports = Data.Text.clone({
    id: "NINumber",
    data_length: 9,
    css_reload: true,           // field must reload
    gender_list: "rm.gender",
    regex_pattern: "^[A-CEGHJ-PR-TW-Z]{1}[A-CEGHJ-NPR-TW-Z]{1}[0-9]{6}[A-DFM]{0,1}$",
    regex_label: "must be a valid UK NI number",
});

module.exports.defbind("setupGenderLov", "cloneInstance", function () {
    var undisclosed_item;
    this.gender_lov = Data.LoV.getListLoV(this.gender_list);
    undisclosed_item = this.gender_lov.getItem("ZZ");
    if (undisclosed_item) {
        undisclosed_item.active = false;
    }
    this.attempting_ni_input = true;            // to begin with
});

module.exports.override("appendClientSideProperties", function (obj) {
    Data.Text.appendClientSideProperties.call(this, obj);
    if (this.attempting_ni_input) {
        obj.min_parts_expected = 2;
        obj.max_parts_expected = 2;
    } else {
        obj.min_parts_expected = 4;
        obj.max_parts_expected = 4;
    }
});

module.exports.override("renderUpdateControls", function (div, render_opts, form_type) {
    if (this.attempting_ni_input) {
        div.makeInput("text", null, this.getUpdateText(),
            this.getInputSizeCSSClass(form_type),
            this.placeholder || this.helper_text);
    } else {
        div.makeInput("text", null, this.unknown_ni_date, "form-control", "dd/MM/yy")
            .attr("style", "display: inline; width: 100px;");
        this.gender_lov.renderRadio(div, render_opts, this.unknown_ni_gender, "form-control",
            true, true);
    }
    div.makeElement("span").text("if NI is unknown:");
    div.makeCheckbox("is_unknown", "Y", !this.attempting_ni_input);
});

module.exports.override("setFromParamValue", function (str) {
    var parts = str.split("|");
    var new_val = "";
    if (this.attempting_ni_input) {
        new_val = parts[0];
    } else {
        new_val = "TN";
        if (parts.length > 1) {
            this.unknown_ni_date = parts[0];
            new_val += parts[0].replace(/\//g, "");
        }
        if (parts.length > 2) {
            this.unknown_ni_gender = parts[1];
            new_val += parts[1];
        }
    }
    this.attempting_ni_input = !(parts[parts.length - 1] === "Y");
    this.set(new_val);
});

module.exports.override("generateTestValue", function (session) {
    return "AB" + Core.Format.getRandomString(6, "0123456789") + "C";
});
<commit_msg>fix server-side NI Number field<commit_after>"use strict";

var Core = require("lapis-core/index.js");
var Data = require("lazuli-data/index.js");

module.exports = Data.Text.clone({
    id: "NINumber",
    data_length: 9,
    css_reload: true,           // field must reload
    gender_list: "rm.gender",
    ni_regex_pattern: "^[A-CEGHJ-PR-TW-Z]{1}[A-CEGHJ-NPR-TW-Z]{1}[0-9]{6}[A-DFM]{0,1}$",
    regex_label: "must be a valid UK NI number",
});

module.exports.defbind("setupGenderLov", "cloneInstance", function () {
    var undisclosed_item;
    this.gender_lov = Data.LoV.getListLoV(this.gender_list);
    undisclosed_item = this.gender_lov.getItem("ZZ");
    if (undisclosed_item) {
        undisclosed_item.active = false;
    }
    this.real_ni_number = true;            // to begin with
});

module.exports.defbind("setWhetherRealNI", "setInitial", function () {
    var val = this.get();
    var date;
    if (val.indexOf("TN") === 0) {
        this.real_ni_number = false;            // first 2 letters TN indicate temp number
        date = Date.parseString(val.substr(2, 6), "ddMMyy");
        if (date) {
            this.unknown_ni_date = date.format("dd/MM/yy");
        }
        this.unknown_ni_gender = val.substr(8, 1);
    }
});

module.exports.defbind("validateTempNI", "validate", function () {
    var val = this.get();
    if (val.indexOf("TN") === 0) {
        if (val.length === 2) {
            this.messages.add({
                type: "E",
                text: "date and gender both required",
            });
        } else if (val.length === 3) {
            this.messages.add({
                type: "E",
                text: "date also required",
            });
        } else if (val.length === 8) {
            this.messages.add({
                type: "E",
                text: "gender also required",
            });
        }
    }
});

module.exports.override("setFromParamValue", function (str) {
    var parts = str.split("|");
    var new_val;
    var orig_use_real_ni_number = this.real_ni_number;
    this.real_ni_number = !(parts[parts.length - 1] === "Y");
    if (this.real_ni_number !== orig_use_real_ni_number) {
        new_val = "";
    } else if (this.real_ni_number) {
        new_val = parts[0];
    } else {
        new_val = "TN";
        if (parts.length > 1) {
            this.unknown_ni_date = parts[0];
            new_val += parts[0].replace(/\//g, "");
        }
        if (parts.length > 2) {
            this.unknown_ni_gender = parts[1];
            new_val += parts[1];
        }
    }
    this.set(new_val);
});

module.exports.override("appendClientSideProperties", function (obj) {
    if (this.real_ni_number) {         // only use the regex if attempting NI number input
        this.regex_pattern = this.ni_regex_pattern;
    } else {
        this.regex_pattern = null;
    }
    Data.Text.appendClientSideProperties.call(this, obj);
    if (this.real_ni_number) {
        obj.min_parts_expected = 2;
        obj.max_parts_expected = 2;
    } else {
        obj.min_parts_expected = 4;
        obj.max_parts_expected = 4;
        obj.data_length = 12;       // accommodate 8-char date, plus the other two fields
    }
});

module.exports.override("renderUpdateControls", function (div, render_opts, form_type) {
    if (this.real_ni_number) {
        div.makeInput("text", null, this.getUpdateText(),
            this.getInputSizeCSSClass(form_type),
            this.placeholder || this.helper_text);
    } else {
        div.makeInput("text", null, this.unknown_ni_date, "form-control", "dd/MM/yy")
            .attr("style", "display: inline; width: 100px;");
        this.gender_lov.renderRadio(div, render_opts, this.unknown_ni_gender, "form-control",
            true, true);
    }
    div.makeElement("span").text("use Temporary NI Number:");
    div.makeCheckbox("is_unknown", "Y", !this.real_ni_number);
});

module.exports.override("generateTestValue", function (session) {
    return "AB" + Core.Format.getRandomString(6, "0123456789") + "C";
});
<|endoftext|>