<commit_before>'use strict';

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

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

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

module.exports = class gemini extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'gemini',
            'name': 'Gemini',
            'countries': [ 'US' ],
            'rateLimit': 1500, // 200 for private API
            'version': 'v1',
            'has': {
                'fetchDepositAddress': false,
                'createDepositAddress': true,
                'CORS': false,
                'fetchBidsAsks': false,
                'fetchTickers': false,
                'fetchMyTrades': true,
                'fetchOrder': true,
                'fetchOrders': false,
                'fetchOpenOrders': false,
                'fetchClosedOrders': false,
                'withdraw': true,
                'fetchTransactions': true,
                'fetchWithdrawals': false,
                'fetchDeposits': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg',
                'api': 'https://api.gemini.com',
                'www': 'https://gemini.com',
                'doc': [
                    'https://docs.gemini.com/rest-api',
                    'https://docs.sandbox.gemini.com',
                ],
                'test': 'https://api.sandbox.gemini.com',
                'fees': [
                    'https://gemini.com/fee-schedule/',
                    'https://gemini.com/transfer-fees/',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'symbols',
                        'pubticker/{symbol}',
                        'book/{symbol}',
                        'trades/{symbol}',
                        'auction/{symbol}',
                        'auction/{symbol}/history',
                    ],
                },
                'private': {
                    'post': [
                        'order/new',
                        'order/cancel',
                        'order/cancel/session',
                        'order/cancel/all',
                        'order/status',
                        'orders',
                        'mytrades',
                        'tradevolume',
                        'transfers',
                        'balances',
                        'deposit/{currency}/newAddress',
                        'withdraw/{currency}',
                        'heartbeat',
                        'transfers',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.0025,
                    'maker': 0.0025,
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetSymbols ();
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let id = markets[p];
            let market = id;
            let uppercase = market.toUpperCase ();
            let base = uppercase.slice (0, 3);
            let quote = uppercase.slice (3, 6);
            let symbol = base + '/' + quote;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
            });
        }
        return result;
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetBookSymbol (this.extend ({
            'symbol': this.marketId (symbol),
        }, params));
        return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetPubtickerSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        let timestamp = ticker['volume']['timestamp'];
        let baseVolume = market['base'];
        let quoteVolume = market['quote'];
        let last = this.safeFloat (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': this.safeFloat (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'ask'),
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': parseFloat (ticker['volume'][baseVolume]),
            'quoteVolume': parseFloat (ticker['volume'][quoteVolume]),
            'info': ticker,
        };
    }

    parseTrade (trade, market) {
        let timestamp = trade['timestampms'];
        let order = undefined;
        if ('order_id' in trade)
            order = trade['order_id'].toString ();
        let fee = this.safeFloat (trade, 'fee_amount');
        if (typeof fee !== 'undefined') {
            let currency = this.safeString (trade, 'fee_currency');
            if (typeof currency !== 'undefined') {
                if (currency in this.currencies_by_id)
                    currency = this.currencies_by_id[currency]['code'];
                currency = this.commonCurrencyCode (currency);
            }
            fee = {
                'cost': this.safeFloat (trade, 'fee_amount'),
                'currency': currency,
            };
        }
        let price = this.safeFloat (trade, 'price');
        let amount = this.safeFloat (trade, 'amount');
        return {
            'id': trade['tid'].toString (),
            'order': order,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['type'],
            'price': price,
            'cost': price * amount,
            'amount': amount,
            'fee': fee,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTradesSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let balances = await this.privatePostBalances ();
        let result = { 'info': balances };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'];
            let account = {
                'free': parseFloat (balance['available']),
                'used': 0.0,
                'total': parseFloat (balance['amount']),
            };
            account['used'] = account['total'] - account['free'];
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    parseOrder (order, market = undefined) {
        let timestamp = order['timestampms'];
        let amount = this.safeFloat (order, 'original_amount');
        let remaining = this.safeFloat (order, 'remaining_amount');
        let filled = this.safeFloat (order, 'executed_amount');
        let status = 'closed';
        if (order['is_live']) {
            status = 'open';
        }
        if (order['is_canceled']) {
            status = 'canceled';
        }
        let price = this.safeFloat (order, 'price');
        price = this.safeFloat (order, 'avg_execution_price', price);
        let cost = undefined;
        if (typeof filled !== 'undefined') {
            if (typeof price !== 'undefined') {
                cost = filled * price;
            }
        }
        let type = this.safeString (order, 'type');
        if (type === 'exchange limit') {
            type = 'limit';
        } else if (type === 'market buy' || type === 'market sell') {
            type = 'market';
        } else {
            type = order['type'];
        }
        let fee = undefined;
        let symbol = undefined;
        if (typeof market === 'undefined') {
            let marketId = this.safeString (order, 'symbol');
            if (marketId in this.markets_by_id) {
                market = this.markets_by_id[marketId];
            }
        }
        if (typeof market !== 'undefined') {
            symbol = market['symbol'];
        }
        return {
            'id': order['order_id'],
            'info': order,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'lastTradeTimestamp': undefined,
            'status': status,
            'symbol': symbol,
            'type': type,
            'side': order['side'],
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'fee': fee,
        };
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostOrderStatus (this.extend ({
            'order_id': id,
        }, params));
        return this.parseOrder (response);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        if (type === 'market')
            throw new ExchangeError (this.id + ' allows limit orders only');
        let nonce = this.nonce ();
        let order = {
            'client_order_id': nonce.toString (),
            'symbol': this.marketId (symbol),
            'amount': amount.toString (),
            'price': price.toString (),
            'side': side,
            'type': 'exchange limit', // gemini allows limit orders only
        };
        let response = await this.privatePostOrderNew (this.extend (order, params));
        return {
            'info': response,
            'id': response['order_id'],
        };
    }

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

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' fetchMyTrades requires a symbol argument');
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
        };
        if (typeof limit !== 'undefined')
            request['limit_trades'] = limit;
        if (typeof since !== 'undefined')
            request['timestamp'] = parseInt (since / 1000);
        let response = await this.privatePostMytrades (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async withdraw (code, amount, address, tag = undefined, params = {}) {
        this.checkAddress (address);
        await this.loadMarkets ();
        let currency = this.currency (code);
        let response = await this.privatePostWithdrawCurrency (this.extend ({
            'currency': currency['id'],
            'amount': amount,
            'address': address,
        }, params));
        return {
            'info': response,
            'id': this.safeString (response, 'txHash'),
        };
    }

    async fetchTransactions (currency = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {};
        let response = await this.privatePostTransfers (this.extend (request, params));
        return this.parseTransactions (response);
    }

    parseTransaction (transaction, side = undefined) {
        let timestamp = this.safeInteger (transaction, 'timestampms');
        let datetime = undefined;
        if (typeof timestamp !== 'undefined')
            datetime = this.iso8601 (timestamp);
        let currency = transaction['currency'];
        if (currency in this.currencies_by_id)
            currency = this.currencies_by_id[currency]['code'];
        if (transaction['type'] === 'Withdrawal')
            side = 'withdraw';
        else if (transaction['type'] === 'Deposit')
            side = 'deposit';
        let status = 'pending';
        // When deposits show as Advanced or Complete they are available for trading.
        if (transaction['status'])
            status = 'ok';
        return {
            'info': transaction,
            'id': this.safeString (transaction, 'eid'),
            'txid': this.safeString (transaction, 'txHash'),
            'timestamp': timestamp,
            'datetime': datetime,
            'currency': currency,
            'status': status,
            'side': side, // direction of the transaction, ('deposit' | 'withdraw')
            'amount': this.safeFloat (transaction, 'amount'),
            'fee': {
                'cost': undefined,
                'rate': undefined,
            },
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = '/' + this.version + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            let request = this.extend ({
                'request': url,
                'nonce': nonce,
            }, query);
            let payload = this.json (request);
            payload = this.stringToBase64 (this.encode (payload));
            let signature = this.hmac (payload, this.encode (this.secret), 'sha384');
            headers = {
                'Content-Type': 'text/plain',
                'X-GEMINI-APIKEY': this.apiKey,
                'X-GEMINI-PAYLOAD': this.decode (payload),
                'X-GEMINI-SIGNATURE': signature,
            };
        }
        url = this.urls['api'] + url;
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('result' in response)
            if (response['result'] === 'error')
                throw new ExchangeError (this.id + ' ' + this.json (response));
        return response;
    }

    async createDepositAddress (code, params = {}) {
        await this.loadMarkets ();
        let currency = this.currency (code);
        let response = await this.privatePostDepositCurrencyNewAddress (this.extend ({
            'currency': currency['id'],
        }, params));
        let address = this.safeString (response, 'address');
        this.checkAddress (address);
        return {
            'currency': code,
            'address': address,
            'info': response,
        };
    }
};
<commit_msg>unified args minor fix<commit_after>'use strict';

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

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

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

module.exports = class gemini extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'gemini',
            'name': 'Gemini',
            'countries': [ 'US' ],
            'rateLimit': 1500, // 200 for private API
            'version': 'v1',
            'has': {
                'fetchDepositAddress': false,
                'createDepositAddress': true,
                'CORS': false,
                'fetchBidsAsks': false,
                'fetchTickers': false,
                'fetchMyTrades': true,
                'fetchOrder': true,
                'fetchOrders': false,
                'fetchOpenOrders': false,
                'fetchClosedOrders': false,
                'withdraw': true,
                'fetchTransactions': true,
                'fetchWithdrawals': false,
                'fetchDeposits': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg',
                'api': 'https://api.gemini.com',
                'www': 'https://gemini.com',
                'doc': [
                    'https://docs.gemini.com/rest-api',
                    'https://docs.sandbox.gemini.com',
                ],
                'test': 'https://api.sandbox.gemini.com',
                'fees': [
                    'https://gemini.com/fee-schedule/',
                    'https://gemini.com/transfer-fees/',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'symbols',
                        'pubticker/{symbol}',
                        'book/{symbol}',
                        'trades/{symbol}',
                        'auction/{symbol}',
                        'auction/{symbol}/history',
                    ],
                },
                'private': {
                    'post': [
                        'order/new',
                        'order/cancel',
                        'order/cancel/session',
                        'order/cancel/all',
                        'order/status',
                        'orders',
                        'mytrades',
                        'tradevolume',
                        'transfers',
                        'balances',
                        'deposit/{currency}/newAddress',
                        'withdraw/{currency}',
                        'heartbeat',
                        'transfers',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.0025,
                    'maker': 0.0025,
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetSymbols ();
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let id = markets[p];
            let market = id;
            let uppercase = market.toUpperCase ();
            let base = uppercase.slice (0, 3);
            let quote = uppercase.slice (3, 6);
            let symbol = base + '/' + quote;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
            });
        }
        return result;
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetBookSymbol (this.extend ({
            'symbol': this.marketId (symbol),
        }, params));
        return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetPubtickerSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        let timestamp = ticker['volume']['timestamp'];
        let baseVolume = market['base'];
        let quoteVolume = market['quote'];
        let last = this.safeFloat (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': this.safeFloat (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'ask'),
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': parseFloat (ticker['volume'][baseVolume]),
            'quoteVolume': parseFloat (ticker['volume'][quoteVolume]),
            'info': ticker,
        };
    }

    parseTrade (trade, market) {
        let timestamp = trade['timestampms'];
        let order = undefined;
        if ('order_id' in trade)
            order = trade['order_id'].toString ();
        let fee = this.safeFloat (trade, 'fee_amount');
        if (typeof fee !== 'undefined') {
            let currency = this.safeString (trade, 'fee_currency');
            if (typeof currency !== 'undefined') {
                if (currency in this.currencies_by_id)
                    currency = this.currencies_by_id[currency]['code'];
                currency = this.commonCurrencyCode (currency);
            }
            fee = {
                'cost': this.safeFloat (trade, 'fee_amount'),
                'currency': currency,
            };
        }
        let price = this.safeFloat (trade, 'price');
        let amount = this.safeFloat (trade, 'amount');
        return {
            'id': trade['tid'].toString (),
            'order': order,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['type'],
            'price': price,
            'cost': price * amount,
            'amount': amount,
            'fee': fee,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTradesSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let balances = await this.privatePostBalances ();
        let result = { 'info': balances };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'];
            let account = {
                'free': parseFloat (balance['available']),
                'used': 0.0,
                'total': parseFloat (balance['amount']),
            };
            account['used'] = account['total'] - account['free'];
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    parseOrder (order, market = undefined) {
        let timestamp = order['timestampms'];
        let amount = this.safeFloat (order, 'original_amount');
        let remaining = this.safeFloat (order, 'remaining_amount');
        let filled = this.safeFloat (order, 'executed_amount');
        let status = 'closed';
        if (order['is_live']) {
            status = 'open';
        }
        if (order['is_canceled']) {
            status = 'canceled';
        }
        let price = this.safeFloat (order, 'price');
        price = this.safeFloat (order, 'avg_execution_price', price);
        let cost = undefined;
        if (typeof filled !== 'undefined') {
            if (typeof price !== 'undefined') {
                cost = filled * price;
            }
        }
        let type = this.safeString (order, 'type');
        if (type === 'exchange limit') {
            type = 'limit';
        } else if (type === 'market buy' || type === 'market sell') {
            type = 'market';
        } else {
            type = order['type'];
        }
        let fee = undefined;
        let symbol = undefined;
        if (typeof market === 'undefined') {
            let marketId = this.safeString (order, 'symbol');
            if (marketId in this.markets_by_id) {
                market = this.markets_by_id[marketId];
            }
        }
        if (typeof market !== 'undefined') {
            symbol = market['symbol'];
        }
        return {
            'id': order['order_id'],
            'info': order,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'lastTradeTimestamp': undefined,
            'status': status,
            'symbol': symbol,
            'type': type,
            'side': order['side'],
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'fee': fee,
        };
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostOrderStatus (this.extend ({
            'order_id': id,
        }, params));
        return this.parseOrder (response);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        if (type === 'market')
            throw new ExchangeError (this.id + ' allows limit orders only');
        let nonce = this.nonce ();
        let order = {
            'client_order_id': nonce.toString (),
            'symbol': this.marketId (symbol),
            'amount': amount.toString (),
            'price': price.toString (),
            'side': side,
            'type': 'exchange limit', // gemini allows limit orders only
        };
        let response = await this.privatePostOrderNew (this.extend (order, params));
        return {
            'info': response,
            'id': response['order_id'],
        };
    }

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

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' fetchMyTrades requires a symbol argument');
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
        };
        if (typeof limit !== 'undefined')
            request['limit_trades'] = limit;
        if (typeof since !== 'undefined')
            request['timestamp'] = parseInt (since / 1000);
        let response = await this.privatePostMytrades (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async withdraw (code, amount, address, tag = undefined, params = {}) {
        this.checkAddress (address);
        await this.loadMarkets ();
        let currency = this.currency (code);
        let response = await this.privatePostWithdrawCurrency (this.extend ({
            'currency': currency['id'],
            'amount': amount,
            'address': address,
        }, params));
        return {
            'info': response,
            'id': this.safeString (response, 'txHash'),
        };
    }

    async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {};
        let response = await this.privatePostTransfers (this.extend (request, params));
        return this.parseTransactions (response);
    }

    parseTransaction (transaction, side = undefined) {
        let timestamp = this.safeInteger (transaction, 'timestampms');
        let datetime = undefined;
        if (typeof timestamp !== 'undefined')
            datetime = this.iso8601 (timestamp);
        let currency = transaction['currency'];
        if (currency in this.currencies_by_id)
            currency = this.currencies_by_id[currency]['code'];
        if (transaction['type'] === 'Withdrawal')
            side = 'withdraw';
        else if (transaction['type'] === 'Deposit')
            side = 'deposit';
        let status = 'pending';
        // When deposits show as Advanced or Complete they are available for trading.
        if (transaction['status'])
            status = 'ok';
        return {
            'info': transaction,
            'id': this.safeString (transaction, 'eid'),
            'txid': this.safeString (transaction, 'txHash'),
            'timestamp': timestamp,
            'datetime': datetime,
            'currency': currency,
            'status': status,
            'side': side, // direction of the transaction, ('deposit' | 'withdraw')
            'amount': this.safeFloat (transaction, 'amount'),
            'fee': {
                'cost': undefined,
                'rate': undefined,
            },
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = '/' + this.version + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            let request = this.extend ({
                'request': url,
                'nonce': nonce,
            }, query);
            let payload = this.json (request);
            payload = this.stringToBase64 (this.encode (payload));
            let signature = this.hmac (payload, this.encode (this.secret), 'sha384');
            headers = {
                'Content-Type': 'text/plain',
                'X-GEMINI-APIKEY': this.apiKey,
                'X-GEMINI-PAYLOAD': this.decode (payload),
                'X-GEMINI-SIGNATURE': signature,
            };
        }
        url = this.urls['api'] + url;
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('result' in response)
            if (response['result'] === 'error')
                throw new ExchangeError (this.id + ' ' + this.json (response));
        return response;
    }

    async createDepositAddress (code, params = {}) {
        await this.loadMarkets ();
        let currency = this.currency (code);
        let response = await this.privatePostDepositCurrencyNewAddress (this.extend ({
            'currency': currency['id'],
        }, params));
        let address = this.safeString (response, 'address');
        this.checkAddress (address);
        return {
            'currency': code,
            'address': address,
            'info': response,
        };
    }
};
<|endoftext|>


<commit_before>var recast = require('recast');
var _      = require('lodash');
var n      = recast.types.namedTypes;
var b      = recast.types.builders;
var fs     = require('fs');
var colors = require('colors');
var Promise = require('bluebird');

var middlewares = [
  require('./modules/convert-require-stmt-to-import-stmt'),
  require('./modules/strip-use-stricts'),
  require('./modules/convert-module-dot-export-to-export-default.js'),
  require('./modules/convert-fn-expr-in-obj-to-shorthand'),
  require('./modules/convert-same-key-value-in-obj-expr-to-just-key')
];

/////////////
// Convert //
/////////////
function convert(pattern, opts) {

  var verbose = opts.verbose;

  var glob = require('glob');
  var path = require('path');
  var exec = Promise.promisify(require('child_process').exec);
  var p = path.join(process.cwd(), pattern, "/**/*.+(jsx|js)");

  if (verbose) {
    console.log("File pattern to search: " + p);
  }

  var files = glob.sync(p);

  if (!files) {
    if (verbose) {
      console.log("Error globbing files.".red);
    }
    return;
  }

  if (!files.length) {
    if (verbose) {
      console.log("No files to convert.".yellow);
    }
  }

  Promise.all(files.map(function(file) {

    return exec('git ls-files ' + file + ' --error-unmatch').then(function() {

      // Read file and parse to ast
      var code   = fs.readFileSync(file, "utf-8");
      var ast    = recast.parse(code);

      if (!code) {
        if (verbose) {
          console.log(file + " is empty or does not exist.".yellow);
        }
        return;
      }

      // Run through middlewares
      ast.program.body = middlewares.reduce(function(body, m) {
        return m(body);
      }, ast.program.body);

      // Write
      fs.writeFileSync(file, _.trimLeft(recast.print(ast).code));

      if (verbose) {
        console.log((file + " is converted.").green);
      }

    })
    .catch(function(e) {
      console.log(e.stack)
      if (verbose) {
        console.log((file + " is not converted because it's not tracked by git.").red);
      }
    });

  }));

}

module.exports = convert;
<commit_msg>remove console.log<commit_after>var recast = require('recast');
var _      = require('lodash');
var n      = recast.types.namedTypes;
var b      = recast.types.builders;
var fs     = require('fs');
var colors = require('colors');
var Promise = require('bluebird');

var middlewares = [
  require('./modules/convert-require-stmt-to-import-stmt'),
  require('./modules/strip-use-stricts'),
  require('./modules/convert-module-dot-export-to-export-default.js'),
  require('./modules/convert-fn-expr-in-obj-to-shorthand'),
  require('./modules/convert-same-key-value-in-obj-expr-to-just-key')
];

/////////////
// Convert //
/////////////
function convert(pattern, opts) {

  var verbose = opts.verbose;

  var glob = require('glob');
  var path = require('path');
  var exec = Promise.promisify(require('child_process').exec);
  var p = path.join(process.cwd(), pattern, "/**/*.+(jsx|js)");

  if (verbose) {
    console.log("File pattern to search: " + p);
  }

  var files = glob.sync(p);

  if (!files) {
    if (verbose) {
      console.log("Error globbing files.".red);
    }
    return;
  }

  if (!files.length) {
    if (verbose) {
      console.log("No files to convert.".yellow);
    }
  }

  Promise.all(files.map(function(file) {

    return exec('git ls-files ' + file + ' --error-unmatch').then(function() {

      // Read file and parse to ast
      var code   = fs.readFileSync(file, "utf-8");
      var ast    = recast.parse(code);

      if (!code) {
        if (verbose) {
          console.log(file + " is empty or does not exist.".yellow);
        }
        return;
      }

      // Run through middlewares
      ast.program.body = middlewares.reduce(function(body, m) {
        return m(body);
      }, ast.program.body);

      // Write
      fs.writeFileSync(file, _.trimLeft(recast.print(ast).code));

      if (verbose) {
        console.log((file + " is converted.").green);
      }

    })
    .catch(function(e) {
      if (verbose) {
        console.log((file + " is not converted because it's not tracked by git.").red);
      }
    });

  }));

}

module.exports = convert;
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options', function() {

  var OptionsPage = options.OptionsPage;

  /////////////////////////////////////////////////////////////////////////////
  // CookiesView class:

  /**
   * Encapsulated handling of ChromeOS accounts options page.
   * @constructor
   */
  function CookiesView(model) {
    OptionsPage.call(this, 'cookiesView',
                     localStrings.getString('cookiesViewPage'),
                     'cookiesViewPage');
  }

  cr.addSingletonGetter(CookiesView);

  CookiesView.prototype = {
    __proto__: OptionsPage.prototype,

    initializePage: function() {
      OptionsPage.prototype.initializePage.call(this);

      var cookiesTree = $('cookiesTree');

      options.CookiesTree.decorate(cookiesTree);
      cookiesTree.addEventListener('change',
          this.handleCookieTreeChange_.bind(this));

      $('cookiesSearchBox').addEventListener('keydown',
          this.handleQueryEditKeyDown_.bind(this));

      var self = this;
      $('clearCookieSearchButton').onclick = function(e) {
        $('cookiesSearchBox').value = '';
        self.searchCookie();
      }

      $('remove-cookie').onclick = function(e) {
        var selected = cookiesTree.selectedItem;
        chrome.send('removeCookie', [selected.pathId]);
      }

      $('remove-all-cookie').onclick = function(e) {
        chrome.send('removeAllCookies', []);
      }

      this.addEventListener('visibleChange', this.handleVisibleChange_);

      this.clearCookieInfo();
    },

    /**
     * Set visible detailed info.
     * @param {string} name Name of the details info pane to made visible.
     */
    updateVisibleDetailedInfo: function(name) {
      const infoPaneNames = [
          'cookiesInfo',
          'appCacheInfo',
          'webDbInfo',
          'localStorageInfo',
          'indexedDBInfo'];

      for (var i = 0 ; i < infoPaneNames.length; ++i) {
        var paneName = infoPaneNames[i];
        var pane = $(paneName);
        if (name == paneName) {
          pane.classList.remove('hidden');
        } else {
          pane.classList.add('hidden');
        }
      }
    },

    /**
     * Update remove button state.
     */
    updateRemoveButtonState: function() {
      var cookiesTree = $('cookiesTree');
      $('remove-cookie').disabled = !cookiesTree.children.length ||
          !cookiesTree.selectedItem;
      $('remove-all-cookie').disabled = !cookiesTree.children.length;
    },

    /**
     * Clears cookie info.
     */
    clearCookieInfo: function() {
      $('cookieName').textContent = localStrings.getString('no_cookie');
      $('cookieContent').textContent = localStrings.getString('no_cookie');
      $('cookieDomain').textContent = localStrings.getString('no_cookie');
      $('cookiePath').textContent = localStrings.getString('no_cookie');
      $('cookieSendFor').textContent = localStrings.getString('no_cookie');
      $('cookieCreated').textContent = localStrings.getString('no_cookie');
      $('cookieExpires').textContent = localStrings.getString('no_cookie');
    },

    /**
     * Sets cookie info to display.
     */
    setCookieInfo: function(cookie) {
      $('cookieName').textContent = cookie.name;
      $('cookieContent').textContent = cookie.content;
      $('cookieDomain').textContent = cookie.domain;
      $('cookiePath').textContent = cookie.path;
      $('cookieSendFor').textContent = cookie.sendfor;
      $('cookieCreated').textContent = cookie.created;
      $('cookieExpires').textContent = cookie.expires;
    },

    /**
     * Sets app cache info to display.
     */
    setAppCacheInfo: function(appCache) {
      $('appCacheManifest').textContent = appCache.manefest;
      $('appCacheSize').textContent = appCache.size;
      $('appCacheCreated').textContent = appCache.created;
      $('appCacheLastAccessed').textContent = appCache.accessed;
    },

    /**
     * Sets web db info to display.
     */
    setWebDbInfo: function(webDb) {
      $('webdbName').textContent = webDb.name;
      $('webdbDesc').textContent = webDb.desc;
      $('webdbSize').textContent = webDb.size;
      $('webdbLastModified').textContent = webDb.modified;
    },

    /**
     * Sets local storage info to display.
     */
    setLocalStorageInfo: function(localStorage) {
      $('localStorageOrigin').textContent = localStorage.origin;
      $('localStorageSize').textContent = localStorage.size;
      $('localStorageLastModified').textContent = localStorage.modified;
    },

    /**
     * Sets IndexedDB info to display.
     */
    setIndexedDBInfo: function(indexedDB) {
      $('indexedDBOrigin').textContent = indexedDB.origin;
      $('indexedDBSize').textContent = indexedDB.size;
      $('indexedDBLastModified').textContent = indexedDB.modified;
    },

    lastQuery_ : null,

    /**
     * Search cookie using text in cookiesSearchBox.
     */
    searchCookie: function() {
      this.queryDelayTimerId_ = 0;
      var filter = $('cookiesSearchBox').value;
      if (this.lastQuery_ != filter) {
        chrome.send('updateCookieSearchResults', [filter]);
        this.lastQuery_ = filter;
      }
    },

    /**
     * Handles cookie tree selection change.
     * @private
     * @param {!Event} e The change event object.
     */
    handleCookieTreeChange_: function(e) {
      this.updateRemoveButtonState();

      var cookiesTree = $('cookiesTree');
      var data = null;
      if (cookiesTree.selectedItem) {
        data = cookiesTree.selectedItem.data;
      }

      if (data && data.type == 'cookie') {
        this.setCookieInfo(data);
        this.updateVisibleDetailedInfo('cookiesInfo');
      } else if (data && data.type == 'database') {
        this.setWebDbInfo(data);
        this.updateVisibleDetailedInfo('webDbInfo');
      } else if (data && data.type == 'local_storage') {
        this.setLocalStorageInfo(data);
        this.updateVisibleDetailedInfo('localStorageInfo');
      } else if (data && data.type == 'app_cache') {
        this.setAppCacheInfo(data);
        this.updateVisibleDetailedInfo('appCacheInfo');
      } else if (data && data.type == 'indexed_db') {
        this.setIndexedDBInfo(data);
        this.updateVisibleDetailedInfo('indexedDBInfo');
      } else {
        this.clearCookieInfo();
        this.updateVisibleDetailedInfo('cookiesInfo');
      }
    },

    queryDelayTimerId_: 0,

    /**
     * Handles query edit key down.
     * @private
     * @param {!Event} e The event object.
     */
    handleQueryEditKeyDown_: function(e) {
      if (this.queryDelayTimerId_) {
        window.clearTimeout(this.queryDelayTimerId_);
      }

      this.queryDelayTimerId_ = window.setTimeout(
          this.searchCookie.bind(this), 500);
    },

    initalized_: false,

    /**
     * Handler for OptionsPage's visible property change event.
     * @private
     * @param {Event} e Property change event.
     */
    handleVisibleChange_: function(e) {
      if (!this.initalized_ && this.visible) {
        this.initalized_ = true;
        this.searchCookie();
      }
    }
  };

  // CookiesViewHandler callbacks.
  CookiesView.onTreeItemAdded = function(args) {
    $('cookiesTree').addByParentId(args[0], args[1], args[2]);
  };

  CookiesView.onTreeItemRemoved = function(args) {
    $('cookiesTree').removeByParentId(args[0], args[1], args[2]);
  };

  CookiesView.loadChildren = function(args) {
    $('cookiesTree').loadChildren(args[0], args[1]);
  };

  // Export
  return {
    CookiesView: CookiesView
  };

});
<commit_msg>Add 'Delete' key to cookies tree in settings UI.<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options', function() {

  var OptionsPage = options.OptionsPage;

  /////////////////////////////////////////////////////////////////////////////
  // CookiesView class:

  /**
   * Encapsulated handling of ChromeOS accounts options page.
   * @constructor
   */
  function CookiesView(model) {
    OptionsPage.call(this, 'cookiesView',
                     localStrings.getString('cookiesViewPage'),
                     'cookiesViewPage');
  }

  cr.addSingletonGetter(CookiesView);

  CookiesView.prototype = {
    __proto__: OptionsPage.prototype,

    initializePage: function() {
      OptionsPage.prototype.initializePage.call(this);

      var cookiesTree = $('cookiesTree');

      options.CookiesTree.decorate(cookiesTree);
      cookiesTree.addEventListener('change',
          this.handleCookieTreeChange_.bind(this));
      cookiesTree.addEventListener('keydown', this.handleKeyDown_);

      $('cookiesSearchBox').addEventListener('keydown',
          this.handleQueryEditKeyDown_.bind(this));

      var self = this;
      $('clearCookieSearchButton').onclick = function(e) {
        $('cookiesSearchBox').value = '';
        self.searchCookie();
      }

      $('remove-cookie').onclick = function(e) {
        var selected = cookiesTree.selectedItem;
        chrome.send('removeCookie', [selected.pathId]);
      }

      $('remove-all-cookie').onclick = function(e) {
        chrome.send('removeAllCookies', []);
      }

      this.addEventListener('visibleChange', this.handleVisibleChange_);

      this.clearCookieInfo();
    },

    /**
     * Set visible detailed info.
     * @param {string} name Name of the details info pane to made visible.
     */
    updateVisibleDetailedInfo: function(name) {
      const infoPaneNames = [
          'cookiesInfo',
          'appCacheInfo',
          'webDbInfo',
          'localStorageInfo',
          'indexedDBInfo'];

      for (var i = 0 ; i < infoPaneNames.length; ++i) {
        var paneName = infoPaneNames[i];
        var pane = $(paneName);
        if (name == paneName) {
          pane.classList.remove('hidden');
        } else {
          pane.classList.add('hidden');
        }
      }
    },

    /**
     * Update remove button state.
     */
    updateRemoveButtonState: function() {
      var cookiesTree = $('cookiesTree');
      $('remove-cookie').disabled = !cookiesTree.children.length ||
          !cookiesTree.selectedItem;
      $('remove-all-cookie').disabled = !cookiesTree.children.length;
    },

    /**
     * Clears cookie info.
     */
    clearCookieInfo: function() {
      $('cookieName').textContent = localStrings.getString('no_cookie');
      $('cookieContent').textContent = localStrings.getString('no_cookie');
      $('cookieDomain').textContent = localStrings.getString('no_cookie');
      $('cookiePath').textContent = localStrings.getString('no_cookie');
      $('cookieSendFor').textContent = localStrings.getString('no_cookie');
      $('cookieCreated').textContent = localStrings.getString('no_cookie');
      $('cookieExpires').textContent = localStrings.getString('no_cookie');
    },

    /**
     * Sets cookie info to display.
     */
    setCookieInfo: function(cookie) {
      $('cookieName').textContent = cookie.name;
      $('cookieContent').textContent = cookie.content;
      $('cookieDomain').textContent = cookie.domain;
      $('cookiePath').textContent = cookie.path;
      $('cookieSendFor').textContent = cookie.sendfor;
      $('cookieCreated').textContent = cookie.created;
      $('cookieExpires').textContent = cookie.expires;
    },

    /**
     * Sets app cache info to display.
     */
    setAppCacheInfo: function(appCache) {
      $('appCacheManifest').textContent = appCache.manefest;
      $('appCacheSize').textContent = appCache.size;
      $('appCacheCreated').textContent = appCache.created;
      $('appCacheLastAccessed').textContent = appCache.accessed;
    },

    /**
     * Sets web db info to display.
     */
    setWebDbInfo: function(webDb) {
      $('webdbName').textContent = webDb.name;
      $('webdbDesc').textContent = webDb.desc;
      $('webdbSize').textContent = webDb.size;
      $('webdbLastModified').textContent = webDb.modified;
    },

    /**
     * Sets local storage info to display.
     */
    setLocalStorageInfo: function(localStorage) {
      $('localStorageOrigin').textContent = localStorage.origin;
      $('localStorageSize').textContent = localStorage.size;
      $('localStorageLastModified').textContent = localStorage.modified;
    },

    /**
     * Sets IndexedDB info to display.
     */
    setIndexedDBInfo: function(indexedDB) {
      $('indexedDBOrigin').textContent = indexedDB.origin;
      $('indexedDBSize').textContent = indexedDB.size;
      $('indexedDBLastModified').textContent = indexedDB.modified;
    },

    lastQuery_ : null,

    /**
     * Search cookie using text in cookiesSearchBox.
     */
    searchCookie: function() {
      this.queryDelayTimerId_ = 0;
      var filter = $('cookiesSearchBox').value;
      if (this.lastQuery_ != filter) {
        chrome.send('updateCookieSearchResults', [filter]);
        this.lastQuery_ = filter;
      }
    },

    /**
     * Handles cookie tree selection change.
     * @private
     * @param {!Event} e The change event object.
     */
    handleCookieTreeChange_: function(e) {
      this.updateRemoveButtonState();

      var cookiesTree = $('cookiesTree');
      var data = null;
      if (cookiesTree.selectedItem) {
        data = cookiesTree.selectedItem.data;
      }

      if (data && data.type == 'cookie') {
        this.setCookieInfo(data);
        this.updateVisibleDetailedInfo('cookiesInfo');
      } else if (data && data.type == 'database') {
        this.setWebDbInfo(data);
        this.updateVisibleDetailedInfo('webDbInfo');
      } else if (data && data.type == 'local_storage') {
        this.setLocalStorageInfo(data);
        this.updateVisibleDetailedInfo('localStorageInfo');
      } else if (data && data.type == 'app_cache') {
        this.setAppCacheInfo(data);
        this.updateVisibleDetailedInfo('appCacheInfo');
      } else if (data && data.type == 'indexed_db') {
        this.setIndexedDBInfo(data);
        this.updateVisibleDetailedInfo('indexedDBInfo');
      } else {
        this.clearCookieInfo();
        this.updateVisibleDetailedInfo('cookiesInfo');
      }
    },

    queryDelayTimerId_: 0,

    /**
     * Handles query edit key down.
     * @private
     * @param {!Event} e The event object.
     */
    handleQueryEditKeyDown_: function(e) {
      if (this.queryDelayTimerId_) {
        window.clearTimeout(this.queryDelayTimerId_);
      }

      this.queryDelayTimerId_ = window.setTimeout(
          this.searchCookie.bind(this), 500);
    },

    initalized_: false,

    /**
     * Handler for OptionsPage's visible property change event.
     * @private
     * @param {Event} e Property change event.
     */
    handleVisibleChange_: function(e) {
      if (!this.initalized_ && this.visible) {
        this.initalized_ = true;
        this.searchCookie();
      }
    },

    /**
     * Handler for keydown event.
     * @private
     * @param {Event} e KeyDown event.
     */
    handleKeyDown_: function(e) {
      // If 'Remove' button is enabled and key is 'Delete' key on all platforms
      // or 'Backspace' on Mac.
      if (!$('remove-cookie').disabled &&
          (e.keyIdentifier == 'U+007F' ||
            (cr.isMac && e.keyIdentifier == 'U+0008'))) {
        e.preventDefault();
        e.stopPropagation();
        cr.dispatchSimpleEvent($('remove-cookie'), 'click');
      }
    }
  };

  // CookiesViewHandler callbacks.
  CookiesView.onTreeItemAdded = function(args) {
    $('cookiesTree').addByParentId(args[0], args[1], args[2]);
  };

  CookiesView.onTreeItemRemoved = function(args) {
    $('cookiesTree').removeByParentId(args[0], args[1], args[2]);
  };

  CookiesView.loadChildren = function(args) {
    $('cookiesTree').loadChildren(args[0], args[1]);
  };

  // Export
  return {
    CookiesView: CookiesView
  };

});
<|endoftext|>


<commit_before>var w = window.innerWidth;
var h = window.innerHeight;
var shapes = [];
var points = [];
var colors = [];
var viewerMode = false;
var i = 0;
var j = 1;
var clear = true;
var bgColor = 0;
var bgChanged = false;
var weight = 5;
var increase = true;
var strokeColors;
var strokeN = 0;
var effectN = 0;
var view = false;

var ref = new Firebase("redraw.firebaseIO.com");
var shapesRef = ref.child("drawings");

function setup() {
  var redColor = color(255, 0, 0);
  var greenColor = color(0, 255, 0);
  var blueColor = color(0, 0, 255);
  var darkGray = color(50, 50, 50);
  var lightGray = color(200, 200, 200);
  strokeColors = new Array(lightGray, darkGray, redColor, greenColor, blueColor);
  createCanvas(w, h);
  smooth();
}

function draw() {
  blockScroll();
  if (clear) {
    background(bgColor);
    clear = false;
  }
  stroke(strokeColors[strokeN]);
  strokeWeight(weight);
  if (bgChanged) {
    for (var k = 0; k < shapes.length; k++) {
      stroke(strokeColors[colors[k]]);
      for (var l = 1; l < shapes[k].length; l++) {
        line(shapes[k][l-1].x*w, shapes[k][l-1].y*h, shapes[k][l].x*w, shapes[k][l].y*h);
      }
    }
    bgChanged = false;

  }
  if (mouseIsPressed && !viewerMode) {
    if (pmouseX != 0 || pmouseY != 0) {
      if (points.length == 0) {
        if (mouseY > 55) {
          line(mouseX, mouseY, mouseX, mouseY);
          points.push({x: mouseX/w, y:mouseY/h});
        }
      } else {
        line(pmouseX, pmouseY, mouseX, mouseY);
        points.push({x: mouseX/w, y:mouseY/h});
      }
    }
  } else if (touchIsDown && !viewerMode) {
    if (ptouchX != 0 && ptouchY != 0) {
      if (points.length == 0) {
        if (touchY > 55) {
          line(touchX, touchY, touchX, touchY);
          points.push({x: touchX/w, y:touchY/h});
        }
      } else {
        line(ptouchX, ptouchY, touchX, touchY);
        points.push({x: touchX/w, y:touchY/h});
      }
    }
  } else {
    points = [];
  }

  if (viewerMode || view) {
    smooth();
    if (i < shapes.length && !view) {
      stroke(strokeColors[colors[i]]);
      if (j < shapes[i].length) {
        line(shapes[i][j-1].x*w, shapes[i][j-1].y*h, shapes[i][j].x*w, shapes[i][j].y*h);
        j++;
      } else {
        i++;
        j = 1;
      }
    } else {
      background(bgColor);
      if (effectN === 0) {
        if (weight > 9) {
          increase = false;
        } else if (weight < 4) {
          increase = true;
        }
        if (increase) {
          weight += 0.3;
        } else {
          weight -= 0.1;
        }
      } else {
        weight = 5;
      }
      for (var k = 0; k < shapes.length; k++) {
        stroke(strokeColors[colors[k]]);
        for (var l = 1; l < shapes[k].length; l++) {
          var x1 = shapes[k][l-1].x*w;
          var y1 = shapes[k][l-1].y*h;
          var x2 = shapes[k][l].x*w;
          var y2 = shapes[k][l].y*h;
          line(x1, y1, x2, y2);
          if (effectN === 1) {
            ellipse(x1+random(4)-random(4), y1+random(4)-random(4), 2+random(2), 2+random(2));
            ellipse(x2+random(4)-random(4), y2+random(4)-random(4), 2+random(2), 2+random(2));
          } else if (effectN === 2) {
            var offset = random(5);
            line(x1+offset, y1+offset, x2+offset, y2+offset);
            offset = random(5);
            line(x1-offset, y1-offset, x2-offset, y2-offset);
          }
        }
      }
    }
  }
}

function mouseReleased() {
  if (points.length != 0) {
    shapes.push(points);
    colors.push(strokeN);
  }
}

function mousePressed() {
  pmouseX = mouseX;
  pmouseY = mouseY;
}

function touchEnded() {
  touchIsDown = false;
  if (points.length != 0) {
    shapes.push(points);
    colors.push(strokeN);
  }
}

function share() {
  var newShapesRef = shapesRef.push();
  newShapesRef.set({shapes: shapes, bgColor: bgColor, colors: colors, effectN: effectN});
  window.open("http://jackbdu.me/redrawer/drawings/?"+newShapesRef.path.o[1]);
}

function clearAll() {
  clear = true;
  shapes = [];
  colors = [];
}

function toggleBackground() {
  if (bgColor == 0) {
    bgColor = 255;
  } else {
    bgColor = 0;
  }
  bgChanged = true;
  clear = true;
}

function changeColor() {
  if (strokeN < strokeColors.length - 1) {
    strokeN++;
  } else {
    strokeN = 0;
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  bgChanged = true;
  clear = true;
  w = window.innerWidth;
  h = window.innerHeight;
}

function viewEdit() {
  view = !view;
}

function changeEffect() {
  if (effectN < 2) {
    effectN++;
  } else {
    effectN = 0;
  }
}

// block scrolling in iphone
function blockScroll() {
  $("body").swipe({
    swipe:function(event, direction, distance, duration, fingerCount) {
      var swipeDirection = direction;
    }
  });
}<commit_msg>curved lines<commit_after>var w = window.innerWidth;
var h = window.innerHeight;
var shapes = [];
var points = [];
var colors = [];
var viewerMode = false;
var i = 0;
var j = 1;
var clear = true;
var bgColor = 0;
var bgChanged = false;
var weight = 5;
var increase = true;
var strokeColors;
var strokeN = 0;
var effectN = 0;
var view = false;

var ref = new Firebase("redraw.firebaseIO.com");
var shapesRef = ref.child("drawings");

var currentLayer;

function setup() {
  var redColor = color(255, 0, 0);
  var greenColor = color(0, 255, 0);
  var blueColor = color(0, 0, 255);
  var darkGray = color(50, 50, 50);
  var lightGray = color(200, 200, 200);
  strokeColors = new Array(lightGray, darkGray, redColor, greenColor, blueColor);
  createCanvas(w, h);
  smooth();
  currentLayer = createGraphics(w, h);
}

function draw() {
  blockScroll();
  noFill();
  if (clear) {
    background(bgColor);
    clear = false;
  }
  strokeWeight(weight);
  if (true) {
    for (var k = 0; k < shapes.length; k++) {
      beginShape();
      stroke(strokeColors[colors[k]]);
      for (var l = 0; l < shapes[k].length; l++) {
        // line(shapes[k][l-1].x*w, shapes[k][l-1].y*h, shapes[k][l].x*w, shapes[k][l].y*h);
        curveVertex(shapes[k][l].x*w, shapes[k][l].y*h);
      }
      endShape();
    }
    stroke(strokeColors[strokeN]);
    beginShape();
    for (var key in points) {
      print(points[key]);
      curveVertex(points[key].x*w, points[key].y*h);
    }
    endShape();
    bgChanged = false;

  }
  if (mouseIsPressed && !viewerMode) {
    if (points.length == 0) {
      if (mouseY > 55) {
        points.push({x: mouseX/w, y:mouseY/h});
      }
    } else {
      points.push({x: mouseX/w, y:mouseY/h});
    }
  } else if (touchIsDown && !viewerMode) {
    if (points.length == 0) {
      if (touchY > 55) {
        points.push({x: touchX/w, y:touchY/h});
      }
    } else {
      points.push({x: touchX/w, y:touchY/h});
    }
  } else {
    points = [];
  }

  if (viewerMode || view) {
    smooth();
    if (i < shapes.length && !view) {
      stroke(strokeColors[colors[i]]);
      if (j < shapes[i].length) {
        line(shapes[i][j-1].x*w, shapes[i][j-1].y*h, shapes[i][j].x*w, shapes[i][j].y*h);
        j++;
      } else {
        i++;
        j = 1;
      }
    } else {
      background(bgColor);
      if (effectN === 0) {
        if (weight > 9) {
          increase = false;
        } else if (weight < 4) {
          increase = true;
        }
        if (increase) {
          weight += 0.3;
        } else {
          weight -= 0.1;
        }
      } else {
        weight = 5;
      }
      for (var k = 0; k < shapes.length; k++) {
        stroke(strokeColors[colors[k]]);
        beginShape();
        for (var l = 0; l < shapes[k].length; l++) {
          var x = shapes[k][l].x*w;
          var y = shapes[k][l].y*h;
          curveVertex(x, y);
          if (effectN === 1) {
            ellipse(x+random(4)-random(4), y+random(4)-random(4), 2+random(2), 2+random(2));
          }
        }
        endShape();
      }
    }
  }
}

function mouseReleased() {
  if (points.length != 0) {
    shapes.push(points);
    colors.push(strokeN);
  }
}

function touchEnded() {
  touchIsDown = false;
  if (points.length != 0) {
    shapes.push(points);
    colors.push(strokeN);
  }
}

function share() {
  var newShapesRef = shapesRef.push();
  newShapesRef.set({shapes: shapes, bgColor: bgColor, colors: colors, effectN: effectN});
  window.open("http://jackbdu.me/redrawer/drawings/?"+newShapesRef.path.o[1]);
}

function clearAll() {
  clear = true;
  shapes = [];
  colors = [];
}

function toggleBackground() {
  if (bgColor == 0) {
    bgColor = 255;
  } else {
    bgColor = 0;
  }
  bgChanged = true;
  clear = true;
}

function changeColor() {
  if (strokeN < strokeColors.length - 1) {
    strokeN++;
  } else {
    strokeN = 0;
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  bgChanged = true;
  clear = true;
  w = window.innerWidth;
  h = window.innerHeight;
}

function viewEdit() {
  view = !view;
}

function changeEffect() {
  if (effectN < 2) {
    effectN++;
  } else {
    effectN = 0;
  }
}

// block scrolling in iphone
function blockScroll() {
  $("body").swipe({
    swipe:function(event, direction, distance, duration, fingerCount) {
      var swipeDirection = direction;
    }
  });
}<|endoftext|>


<commit_before>// @flow strict

import { type IncomingMessage, type ServerResponse } from 'http';
import url from 'url';
import accepts from 'accepts';
import httpError from 'http-errors';
import {
  Source,
  parse,
  validate,
  execute,
  formatError,
  validateSchema,
  getOperationAST,
  specifiedRules,
  type ASTVisitor,
  type DocumentNode,
  type ValidationContext,
  type ExecutionArgs,
  type ExecutionResult,
  type GraphQLError,
  type GraphQLSchema,
  type GraphQLFieldResolver,
  type GraphQLTypeResolver,
} from 'graphql';

import { parseBody } from './parseBody';
import { renderGraphiQL, type GraphiQLOptions } from './renderGraphiQL';

type $Request = IncomingMessage;
type $Response = ServerResponse & {| json?: ?(data: mixed) => void |};

/**
 * Used to configure the graphqlHTTP middleware by providing a schema
 * and other configuration options.
 *
 * Options can be provided as an Object, a Promise for an Object, or a Function
 * that returns an Object or a Promise for an Object.
 */
export type Options =
  | ((
      request: $Request,
      response: $Response,
      params?: GraphQLParams,
    ) => OptionsResult)
  | OptionsResult;
export type OptionsResult = OptionsData | Promise<OptionsData>;

export type OptionsData = {|
  /**
   * A GraphQL schema from graphql-js.
   */
  schema: GraphQLSchema,

  /**
   * A value to pass as the context to this middleware.
   */
  context?: ?mixed,

  /**
   * An object to pass as the rootValue to the graphql() function.
   */
  rootValue?: ?mixed,

  /**
   * A boolean to configure whether the output should be pretty-printed.
   */
  pretty?: ?boolean,

  /**
   * An optional array of validation rules that will be applied on the document
   * in additional to those defined by the GraphQL spec.
   */
  validationRules?: ?Array<(ValidationContext) => ASTVisitor>,

  /**
   * An optional function which will be used to validate instead of default `validate`
   * from `graphql-js`.
   */
  customValidateFn?: ?(
    schema: GraphQLSchema,
    documentAST: DocumentNode,
    rules: $ReadOnlyArray<any>,
  ) => $ReadOnlyArray<GraphQLError>,

  /**
   * An optional function which will be used to execute instead of default `execute`
   * from `graphql-js`.
   */
  customExecuteFn?: ?(args: ExecutionArgs) => Promise<ExecutionResult>,

  /**
   * An optional function which will be used to format any errors produced by
   * fulfilling a GraphQL operation. If no function is provided, GraphQL's
   * default spec-compliant `formatError` function will be used.
   */
  customFormatErrorFn?: ?(error: GraphQLError) => mixed,

  /**
   * An optional function which will be used to create a document instead of
   * the default `parse` from `graphql-js`.
   */
  customParseFn?: ?(source: Source) => DocumentNode,

  /**
   * `formatError` is deprecated and replaced by `customFormatErrorFn`. It will
   *  be removed in version 1.0.0.
   */
  formatError?: ?(error: GraphQLError) => mixed,

  /**
   * An optional function for adding additional metadata to the GraphQL response
   * as a key-value object. The result will be added to "extensions" field in
   * the resulting JSON. This is often a useful place to add development time
   * info such as the runtime of a query or the amount of resources consumed.
   *
   * Information about the request is provided to be used.
   *
   * This function may be async.
   */
  extensions?: ?(info: RequestInfo) => { [key: string]: mixed, ... },

  /**
   * A boolean to optionally enable GraphiQL mode.
   * Alternatively, instead of `true` you can pass in an options object.
   */
  graphiql?: ?boolean | ?GraphiQLOptions,

  /**
   * A resolver function to use when one is not provided by the schema.
   * If not provided, the default field resolver is used (which looks for a
   * value or method on the source value with the field's name).
   */
  fieldResolver?: ?GraphQLFieldResolver<any, any>,

  /**
   * A type resolver function to use when none is provided by the schema.
   * If not provided, the default type resolver is used (which looks for a
   * `__typename` field or alternatively calls the `isTypeOf` method).
   */
  typeResolver?: ?GraphQLTypeResolver<any, any>,
|};

/**
 * All information about a GraphQL request.
 */
export type RequestInfo = {|
  /**
   * The parsed GraphQL document.
   */
  document: ?DocumentNode,

  /**
   * The variable values used at runtime.
   */
  variables: ?{ +[name: string]: mixed, ... },

  /**
   * The (optional) operation name requested.
   */
  operationName: ?string,

  /**
   * The result of executing the operation.
   */
  result: ?mixed,

  /**
   * A value to pass as the context to the graphql() function.
   */
  context?: ?mixed,
|};

type Middleware = (request: $Request, response: $Response) => Promise<void>;

/**
 * Middleware for express; takes an options object or function as input to
 * configure behavior, and returns an express middleware.
 */
module.exports = graphqlHTTP;
function graphqlHTTP(options: Options): Middleware {
  if (!options) {
    throw new Error('GraphQL middleware requires options.');
  }

  return function graphqlMiddleware(request: $Request, response: $Response) {
    // Higher scoped variables are referred to at various stages in the
    // asynchronous state machine below.
    let context;
    let params;
    let pretty;
    let formatErrorFn = formatError;
    let validateFn = validate;
    let executeFn = execute;
    let parseFn = parse;
    let extensionsFn;
    let showGraphiQL = false;
    let query;

    let documentAST;
    let variables;
    let operationName;

    // Promises are used as a mechanism for capturing any thrown errors during
    // the asynchronous process below.

    // Parse the Request to get GraphQL request parameters.
    return getGraphQLParams(request)
      .then(
        graphQLParams => {
          params = graphQLParams;
          // Then, resolve the Options to get OptionsData.
          return resolveOptions(params);
        },
        error => {
          // When we failed to parse the GraphQL parameters, we still need to get
          // the options object, so make an options call to resolve just that.
          const dummyParams = {
            query: null,
            variables: null,
            operationName: null,
            raw: null,
          };
          return resolveOptions(dummyParams).then(() => Promise.reject(error));
        },
      )
      .then(optionsData => {
        // Assert that schema is required.
        if (!optionsData.schema) {
          throw new Error('GraphQL middleware options must contain a schema.');
        }

        // Collect information from the options data object.
        const schema = optionsData.schema;
        const rootValue = optionsData.rootValue;
        const fieldResolver = optionsData.fieldResolver;
        const typeResolver = optionsData.typeResolver;
        const validationRules = optionsData.validationRules || [];
        const graphiql = optionsData.graphiql;
        context = optionsData.context || request;

        // GraphQL HTTP only supports GET and POST methods.
        if (request.method !== 'GET' && request.method !== 'POST') {
          response.setHeader('Allow', 'GET, POST');
          throw httpError(405, 'GraphQL only supports GET and POST requests.');
        }

        // Get GraphQL params from the request and POST body data.
        query = params.query;
        variables = params.variables;
        operationName = params.operationName;
        showGraphiQL = canDisplayGraphiQL(request, params) && graphiql;

        // If there is no query, but GraphiQL will be displayed, do not produce
        // a result, otherwise return a 400: Bad Request.
        if (!query) {
          if (showGraphiQL) {
            return null;
          }
          throw httpError(400, 'Must provide query string.');
        }

        // Validate Schema
        const schemaValidationErrors = validateSchema(schema);
        if (schemaValidationErrors.length > 0) {
          // Return 500: Internal Server Error if invalid schema.
          response.statusCode = 500;
          return { errors: schemaValidationErrors };
        }

        //  GraphQL source.
        const source = new Source(query, 'GraphQL request');

        // Parse source to AST, reporting any syntax error.
        try {
          documentAST = parseFn(source);
        } catch (syntaxError) {
          // Return 400: Bad Request if any syntax errors errors exist.
          response.statusCode = 400;
          return { errors: [syntaxError] };
        }

        // Validate AST, reporting any errors.
        const validationErrors = validateFn(schema, documentAST, [
          ...specifiedRules,
          ...validationRules,
        ]);

        if (validationErrors.length > 0) {
          // Return 400: Bad Request if any validation errors exist.
          response.statusCode = 400;
          return { errors: validationErrors };
        }

        // Only query operations are allowed on GET requests.
        if (request.method === 'GET') {
          // Determine if this GET request will perform a non-query.
          const operationAST = getOperationAST(documentAST, operationName);
          if (operationAST && operationAST.operation !== 'query') {
            // If GraphiQL can be shown, do not perform this query, but
            // provide it to GraphiQL so that the requester may perform it
            // themselves if desired.
            if (showGraphiQL) {
              return null;
            }

            // Otherwise, report a 405: Method Not Allowed error.
            response.setHeader('Allow', 'POST');
            throw httpError(
              405,
              `Can only perform a ${operationAST.operation} operation ` +
                'from a POST request.',
            );
          }
        }
        // Perform the execution, reporting any errors creating the context.
        try {
          return executeFn({
            schema,
            document: documentAST,
            rootValue,
            contextValue: context,
            variableValues: variables,
            operationName,
            fieldResolver,
            typeResolver,
          });
        } catch (contextError) {
          // Return 400: Bad Request if any execution context errors exist.
          response.statusCode = 400;
          return { errors: [contextError] };
        }
      })
      .then(result => {
        // Collect and apply any metadata extensions if a function was provided.
        // http://graphql.github.io/graphql-spec/#sec-Response-Format
        if (result && extensionsFn) {
          return Promise.resolve(
            extensionsFn({
              document: documentAST,
              variables,
              operationName,
              result,
              context,
            }),
          ).then(extensions => {
            if (extensions && typeof extensions === 'object') {
              (result: any).extensions = extensions;
            }
            return result;
          });
        }
        return result;
      })
      .catch(error => {
        // If an error was caught, report the httpError status, or 500.
        response.statusCode = error.status || 500;
        return { errors: [error] };
      })
      .then(result => {
        // If no data was included in the result, that indicates a runtime query
        // error, indicate as such with a generic status code.
        // Note: Information about the error itself will still be contained in
        // the resulting JSON payload.
        // http://graphql.github.io/graphql-spec/#sec-Data
        if (response.statusCode === 200 && result && !result.data) {
          response.statusCode = 500;
        }
        // Format any encountered errors.
        if (result && result.errors) {
          (result: any).errors = result.errors.map(formatErrorFn);
        }

        // If allowed to show GraphiQL, present it instead of JSON.
        if (showGraphiQL) {
          const payload = renderGraphiQL({
            query,
            variables,
            operationName,
            result,
            options: typeof showGraphiQL !== 'boolean' ? showGraphiQL : {},
          });
          return sendResponse(response, 'text/html', payload);
        }

        // At this point, result is guaranteed to exist, as the only scenario
        // where it will not is when showGraphiQL is true.
        if (!result) {
          throw httpError(500, 'Internal Error');
        }

        // If "pretty" JSON isn't requested, and the server provides a
        // response.json method (express), use that directly.
        // Otherwise use the simplified sendResponse method.
        if (!pretty && typeof response.json === 'function') {
          response.json(result);
        } else {
          const payload = JSON.stringify(result, null, pretty ? 2 : 0);
          sendResponse(response, 'application/json', payload);
        }
      });

    async function resolveOptions(requestParams) {
      const optionsResult =
        typeof options === 'function'
          ? options(request, response, requestParams)
          : options;
      const optionsData = await optionsResult;

      // Assert that optionsData is in fact an Object.
      if (!optionsData || typeof optionsData !== 'object') {
        throw new Error(
          'GraphQL middleware option function must return an options object ' +
            'or a promise which will be resolved to an options object.',
        );
      }

      if (optionsData.formatError) {
        // eslint-disable-next-line no-console
        console.warn(
          '`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.',
        );
      }

      validateFn = optionsData.customValidateFn || validateFn;
      executeFn = optionsData.customExecuteFn || executeFn;
      parseFn = optionsData.customParseFn || parseFn;
      formatErrorFn =
        optionsData.customFormatErrorFn ||
        optionsData.formatError ||
        formatErrorFn;
      extensionsFn = optionsData.extensions;
      pretty = optionsData.pretty;
      return optionsData;
    }
  };
}

export type GraphQLParams = {|
  query: ?string,
  variables: ?{ +[name: string]: mixed, ... },
  operationName: ?string,
  raw: ?boolean,
|};

/**
 * Provided a "Request" provided by express or connect (typically a node style
 * HTTPClientRequest), Promise the GraphQL request parameters.
 */
module.exports.getGraphQLParams = getGraphQLParams;
async function getGraphQLParams(request: $Request): Promise<GraphQLParams> {
  const bodyData = await parseBody(request);
  const urlData = (request.url && url.parse(request.url, true).query) || {};

  return parseGraphQLParams(urlData, bodyData);
}

/**
 * Helper function to get the GraphQL params from the request.
 */
function parseGraphQLParams(
  urlData: { [param: string]: mixed, ... },
  bodyData: { [param: string]: mixed, ... },
): GraphQLParams {
  // GraphQL Query string.
  let query = urlData.query || bodyData.query;
  if (typeof query !== 'string') {
    query = null;
  }

  // Parse the variables if needed.
  let variables = urlData.variables || bodyData.variables;
  if (variables && typeof variables === 'string') {
    try {
      variables = JSON.parse(variables);
    } catch (error) {
      throw httpError(400, 'Variables are invalid JSON.');
    }
  } else if (typeof variables !== 'object') {
    variables = null;
  }

  // Name of GraphQL operation to execute.
  let operationName = urlData.operationName || bodyData.operationName;
  if (typeof operationName !== 'string') {
    operationName = null;
  }

  const raw = urlData.raw !== undefined || bodyData.raw !== undefined;

  return { query, variables, operationName, raw };
}

/**
 * Helper function to determine if GraphiQL can be displayed.
 */
function canDisplayGraphiQL(request: $Request, params: GraphQLParams): boolean {
  // If `raw` exists, GraphiQL mode is not enabled.
  // Allowed to show GraphiQL if not requested as raw and this request
  // prefers HTML over JSON.
  return !params.raw && accepts(request).types(['json', 'html']) === 'html';
}

/**
 * Helper function for sending a response using only the core Node server APIs.
 */
function sendResponse(response: $Response, type: string, data: string): void {
  const chunk = new Buffer(data, 'utf8');
  response.setHeader('Content-Type', type + '; charset=utf-8');
  response.setHeader('Content-Length', String(chunk.length));
  response.end(chunk);
}
<commit_msg>Flow: Use $ReadOnlyArray for 'validationRules' option (#529)<commit_after>// @flow strict

import { type IncomingMessage, type ServerResponse } from 'http';
import url from 'url';
import accepts from 'accepts';
import httpError from 'http-errors';
import {
  Source,
  parse,
  validate,
  execute,
  formatError,
  validateSchema,
  getOperationAST,
  specifiedRules,
  type ASTVisitor,
  type DocumentNode,
  type ValidationContext,
  type ExecutionArgs,
  type ExecutionResult,
  type GraphQLError,
  type GraphQLSchema,
  type GraphQLFieldResolver,
  type GraphQLTypeResolver,
} from 'graphql';

import { parseBody } from './parseBody';
import { renderGraphiQL, type GraphiQLOptions } from './renderGraphiQL';

type $Request = IncomingMessage;
type $Response = ServerResponse & {| json?: ?(data: mixed) => void |};

/**
 * Used to configure the graphqlHTTP middleware by providing a schema
 * and other configuration options.
 *
 * Options can be provided as an Object, a Promise for an Object, or a Function
 * that returns an Object or a Promise for an Object.
 */
export type Options =
  | ((
      request: $Request,
      response: $Response,
      params?: GraphQLParams,
    ) => OptionsResult)
  | OptionsResult;
export type OptionsResult = OptionsData | Promise<OptionsData>;

export type OptionsData = {|
  /**
   * A GraphQL schema from graphql-js.
   */
  schema: GraphQLSchema,

  /**
   * A value to pass as the context to this middleware.
   */
  context?: ?mixed,

  /**
   * An object to pass as the rootValue to the graphql() function.
   */
  rootValue?: ?mixed,

  /**
   * A boolean to configure whether the output should be pretty-printed.
   */
  pretty?: ?boolean,

  /**
   * An optional array of validation rules that will be applied on the document
   * in additional to those defined by the GraphQL spec.
   */
  validationRules?: ?$ReadOnlyArray<(ValidationContext) => ASTVisitor>,

  /**
   * An optional function which will be used to validate instead of default `validate`
   * from `graphql-js`.
   */
  customValidateFn?: ?(
    schema: GraphQLSchema,
    documentAST: DocumentNode,
    rules: $ReadOnlyArray<any>,
  ) => $ReadOnlyArray<GraphQLError>,

  /**
   * An optional function which will be used to execute instead of default `execute`
   * from `graphql-js`.
   */
  customExecuteFn?: ?(args: ExecutionArgs) => Promise<ExecutionResult>,

  /**
   * An optional function which will be used to format any errors produced by
   * fulfilling a GraphQL operation. If no function is provided, GraphQL's
   * default spec-compliant `formatError` function will be used.
   */
  customFormatErrorFn?: ?(error: GraphQLError) => mixed,

  /**
   * An optional function which will be used to create a document instead of
   * the default `parse` from `graphql-js`.
   */
  customParseFn?: ?(source: Source) => DocumentNode,

  /**
   * `formatError` is deprecated and replaced by `customFormatErrorFn`. It will
   *  be removed in version 1.0.0.
   */
  formatError?: ?(error: GraphQLError) => mixed,

  /**
   * An optional function for adding additional metadata to the GraphQL response
   * as a key-value object. The result will be added to "extensions" field in
   * the resulting JSON. This is often a useful place to add development time
   * info such as the runtime of a query or the amount of resources consumed.
   *
   * Information about the request is provided to be used.
   *
   * This function may be async.
   */
  extensions?: ?(info: RequestInfo) => { [key: string]: mixed, ... },

  /**
   * A boolean to optionally enable GraphiQL mode.
   * Alternatively, instead of `true` you can pass in an options object.
   */
  graphiql?: ?boolean | ?GraphiQLOptions,

  /**
   * A resolver function to use when one is not provided by the schema.
   * If not provided, the default field resolver is used (which looks for a
   * value or method on the source value with the field's name).
   */
  fieldResolver?: ?GraphQLFieldResolver<any, any>,

  /**
   * A type resolver function to use when none is provided by the schema.
   * If not provided, the default type resolver is used (which looks for a
   * `__typename` field or alternatively calls the `isTypeOf` method).
   */
  typeResolver?: ?GraphQLTypeResolver<any, any>,
|};

/**
 * All information about a GraphQL request.
 */
export type RequestInfo = {|
  /**
   * The parsed GraphQL document.
   */
  document: ?DocumentNode,

  /**
   * The variable values used at runtime.
   */
  variables: ?{ +[name: string]: mixed, ... },

  /**
   * The (optional) operation name requested.
   */
  operationName: ?string,

  /**
   * The result of executing the operation.
   */
  result: ?mixed,

  /**
   * A value to pass as the context to the graphql() function.
   */
  context?: ?mixed,
|};

type Middleware = (request: $Request, response: $Response) => Promise<void>;

/**
 * Middleware for express; takes an options object or function as input to
 * configure behavior, and returns an express middleware.
 */
module.exports = graphqlHTTP;
function graphqlHTTP(options: Options): Middleware {
  if (!options) {
    throw new Error('GraphQL middleware requires options.');
  }

  return function graphqlMiddleware(request: $Request, response: $Response) {
    // Higher scoped variables are referred to at various stages in the
    // asynchronous state machine below.
    let context;
    let params;
    let pretty;
    let formatErrorFn = formatError;
    let validateFn = validate;
    let executeFn = execute;
    let parseFn = parse;
    let extensionsFn;
    let showGraphiQL = false;
    let query;

    let documentAST;
    let variables;
    let operationName;

    // Promises are used as a mechanism for capturing any thrown errors during
    // the asynchronous process below.

    // Parse the Request to get GraphQL request parameters.
    return getGraphQLParams(request)
      .then(
        graphQLParams => {
          params = graphQLParams;
          // Then, resolve the Options to get OptionsData.
          return resolveOptions(params);
        },
        error => {
          // When we failed to parse the GraphQL parameters, we still need to get
          // the options object, so make an options call to resolve just that.
          const dummyParams = {
            query: null,
            variables: null,
            operationName: null,
            raw: null,
          };
          return resolveOptions(dummyParams).then(() => Promise.reject(error));
        },
      )
      .then(optionsData => {
        // Assert that schema is required.
        if (!optionsData.schema) {
          throw new Error('GraphQL middleware options must contain a schema.');
        }

        // Collect information from the options data object.
        const schema = optionsData.schema;
        const rootValue = optionsData.rootValue;
        const fieldResolver = optionsData.fieldResolver;
        const typeResolver = optionsData.typeResolver;
        const validationRules = optionsData.validationRules || [];
        const graphiql = optionsData.graphiql;
        context = optionsData.context || request;

        // GraphQL HTTP only supports GET and POST methods.
        if (request.method !== 'GET' && request.method !== 'POST') {
          response.setHeader('Allow', 'GET, POST');
          throw httpError(405, 'GraphQL only supports GET and POST requests.');
        }

        // Get GraphQL params from the request and POST body data.
        query = params.query;
        variables = params.variables;
        operationName = params.operationName;
        showGraphiQL = canDisplayGraphiQL(request, params) && graphiql;

        // If there is no query, but GraphiQL will be displayed, do not produce
        // a result, otherwise return a 400: Bad Request.
        if (!query) {
          if (showGraphiQL) {
            return null;
          }
          throw httpError(400, 'Must provide query string.');
        }

        // Validate Schema
        const schemaValidationErrors = validateSchema(schema);
        if (schemaValidationErrors.length > 0) {
          // Return 500: Internal Server Error if invalid schema.
          response.statusCode = 500;
          return { errors: schemaValidationErrors };
        }

        //  GraphQL source.
        const source = new Source(query, 'GraphQL request');

        // Parse source to AST, reporting any syntax error.
        try {
          documentAST = parseFn(source);
        } catch (syntaxError) {
          // Return 400: Bad Request if any syntax errors errors exist.
          response.statusCode = 400;
          return { errors: [syntaxError] };
        }

        // Validate AST, reporting any errors.
        const validationErrors = validateFn(schema, documentAST, [
          ...specifiedRules,
          ...validationRules,
        ]);

        if (validationErrors.length > 0) {
          // Return 400: Bad Request if any validation errors exist.
          response.statusCode = 400;
          return { errors: validationErrors };
        }

        // Only query operations are allowed on GET requests.
        if (request.method === 'GET') {
          // Determine if this GET request will perform a non-query.
          const operationAST = getOperationAST(documentAST, operationName);
          if (operationAST && operationAST.operation !== 'query') {
            // If GraphiQL can be shown, do not perform this query, but
            // provide it to GraphiQL so that the requester may perform it
            // themselves if desired.
            if (showGraphiQL) {
              return null;
            }

            // Otherwise, report a 405: Method Not Allowed error.
            response.setHeader('Allow', 'POST');
            throw httpError(
              405,
              `Can only perform a ${operationAST.operation} operation ` +
                'from a POST request.',
            );
          }
        }
        // Perform the execution, reporting any errors creating the context.
        try {
          return executeFn({
            schema,
            document: documentAST,
            rootValue,
            contextValue: context,
            variableValues: variables,
            operationName,
            fieldResolver,
            typeResolver,
          });
        } catch (contextError) {
          // Return 400: Bad Request if any execution context errors exist.
          response.statusCode = 400;
          return { errors: [contextError] };
        }
      })
      .then(result => {
        // Collect and apply any metadata extensions if a function was provided.
        // http://graphql.github.io/graphql-spec/#sec-Response-Format
        if (result && extensionsFn) {
          return Promise.resolve(
            extensionsFn({
              document: documentAST,
              variables,
              operationName,
              result,
              context,
            }),
          ).then(extensions => {
            if (extensions && typeof extensions === 'object') {
              (result: any).extensions = extensions;
            }
            return result;
          });
        }
        return result;
      })
      .catch(error => {
        // If an error was caught, report the httpError status, or 500.
        response.statusCode = error.status || 500;
        return { errors: [error] };
      })
      .then(result => {
        // If no data was included in the result, that indicates a runtime query
        // error, indicate as such with a generic status code.
        // Note: Information about the error itself will still be contained in
        // the resulting JSON payload.
        // http://graphql.github.io/graphql-spec/#sec-Data
        if (response.statusCode === 200 && result && !result.data) {
          response.statusCode = 500;
        }
        // Format any encountered errors.
        if (result && result.errors) {
          (result: any).errors = result.errors.map(formatErrorFn);
        }

        // If allowed to show GraphiQL, present it instead of JSON.
        if (showGraphiQL) {
          const payload = renderGraphiQL({
            query,
            variables,
            operationName,
            result,
            options: typeof showGraphiQL !== 'boolean' ? showGraphiQL : {},
          });
          return sendResponse(response, 'text/html', payload);
        }

        // At this point, result is guaranteed to exist, as the only scenario
        // where it will not is when showGraphiQL is true.
        if (!result) {
          throw httpError(500, 'Internal Error');
        }

        // If "pretty" JSON isn't requested, and the server provides a
        // response.json method (express), use that directly.
        // Otherwise use the simplified sendResponse method.
        if (!pretty && typeof response.json === 'function') {
          response.json(result);
        } else {
          const payload = JSON.stringify(result, null, pretty ? 2 : 0);
          sendResponse(response, 'application/json', payload);
        }
      });

    async function resolveOptions(requestParams) {
      const optionsResult =
        typeof options === 'function'
          ? options(request, response, requestParams)
          : options;
      const optionsData = await optionsResult;

      // Assert that optionsData is in fact an Object.
      if (!optionsData || typeof optionsData !== 'object') {
        throw new Error(
          'GraphQL middleware option function must return an options object ' +
            'or a promise which will be resolved to an options object.',
        );
      }

      if (optionsData.formatError) {
        // eslint-disable-next-line no-console
        console.warn(
          '`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.',
        );
      }

      validateFn = optionsData.customValidateFn || validateFn;
      executeFn = optionsData.customExecuteFn || executeFn;
      parseFn = optionsData.customParseFn || parseFn;
      formatErrorFn =
        optionsData.customFormatErrorFn ||
        optionsData.formatError ||
        formatErrorFn;
      extensionsFn = optionsData.extensions;
      pretty = optionsData.pretty;
      return optionsData;
    }
  };
}

export type GraphQLParams = {|
  query: ?string,
  variables: ?{ +[name: string]: mixed, ... },
  operationName: ?string,
  raw: ?boolean,
|};

/**
 * Provided a "Request" provided by express or connect (typically a node style
 * HTTPClientRequest), Promise the GraphQL request parameters.
 */
module.exports.getGraphQLParams = getGraphQLParams;
async function getGraphQLParams(request: $Request): Promise<GraphQLParams> {
  const bodyData = await parseBody(request);
  const urlData = (request.url && url.parse(request.url, true).query) || {};

  return parseGraphQLParams(urlData, bodyData);
}

/**
 * Helper function to get the GraphQL params from the request.
 */
function parseGraphQLParams(
  urlData: { [param: string]: mixed, ... },
  bodyData: { [param: string]: mixed, ... },
): GraphQLParams {
  // GraphQL Query string.
  let query = urlData.query || bodyData.query;
  if (typeof query !== 'string') {
    query = null;
  }

  // Parse the variables if needed.
  let variables = urlData.variables || bodyData.variables;
  if (variables && typeof variables === 'string') {
    try {
      variables = JSON.parse(variables);
    } catch (error) {
      throw httpError(400, 'Variables are invalid JSON.');
    }
  } else if (typeof variables !== 'object') {
    variables = null;
  }

  // Name of GraphQL operation to execute.
  let operationName = urlData.operationName || bodyData.operationName;
  if (typeof operationName !== 'string') {
    operationName = null;
  }

  const raw = urlData.raw !== undefined || bodyData.raw !== undefined;

  return { query, variables, operationName, raw };
}

/**
 * Helper function to determine if GraphiQL can be displayed.
 */
function canDisplayGraphiQL(request: $Request, params: GraphQLParams): boolean {
  // If `raw` exists, GraphiQL mode is not enabled.
  // Allowed to show GraphiQL if not requested as raw and this request
  // prefers HTML over JSON.
  return !params.raw && accepts(request).types(['json', 'html']) === 'html';
}

/**
 * Helper function for sending a response using only the core Node server APIs.
 */
function sendResponse(response: $Response, type: string, data: string): void {
  const chunk = new Buffer(data, 'utf8');
  response.setHeader('Content-Type', type + '; charset=utf-8');
  response.setHeader('Content-Length', String(chunk.length));
  response.end(chunk);
}
<|endoftext|>