diff --git a/.eslintrc b/.eslintrc index 07f9d69..b2015fa 100755 --- a/.eslintrc +++ b/.eslintrc @@ -91,7 +91,7 @@ "operator-assignment": [1, "never"], "operator-linebreak": 0, "quote-props": 0, - "quotes": [1, "single", "avoid-escape"], + "quotes": [1, "single", { "allowTemplateLiterals": true }], "keyword-spacing": [1, { "before": true, "after": true, diff --git a/.flowconfig b/.flowconfig index 0ae30f9..cbbfbc1 100644 --- a/.flowconfig +++ b/.flowconfig @@ -12,3 +12,7 @@ module.name_mapper='^~/\(.*\)' -> '/src/client/\1' module.name_mapper='^~/\(.*\)' -> '/src/server/\1' module.name_mapper='^shared/\(.*\)' -> '/src/shared/\1' module.name_mapper='^settings$' -> '/settings.js' +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe +suppress_type=$FlowTODO +suppress_type=$FlowTodo +suppress_type=$TODO diff --git a/public/index.html b/public/index.html index 2a0ce26..6294f7e 100755 --- a/public/index.html +++ b/public/index.html @@ -30,5 +30,6 @@ }); } + diff --git a/public/styles.css b/public/styles.css index ec916ad..7ea5d39 100644 --- a/public/styles.css +++ b/public/styles.css @@ -19,6 +19,7 @@ div { #app { height: 100%; + padding-bottom: 48px; } /* Let's get this party started */ ::-webkit-scrollbar { diff --git a/settings.js b/settings.js index 1787c45..b419dbe 100755 --- a/settings.js +++ b/settings.js @@ -5,6 +5,7 @@ require('dotenv').config(); module.exports = Object.assign({}, settingsPublic, { APP_ROOT: __dirname, APP_PORT: process.env.PORT || 3000, + DEBUG: isDebug(), MONGO_URI: process.env.MONGO_URI || 'mongodb://mongo/app', MONGO_TEST_URI: process.env.MONGO_TEST_URI || 'mongodb://mongo/app_test', MONGO_USERNAME: process.env.MONGO_USERNAME, @@ -14,5 +15,18 @@ module.exports = Object.assign({}, settingsPublic, { SHAPESHIFT_SECRET: process.env.SHAPESHIFT_SECRET, LOGGLY_SUBDOMAIN: 'icostats', LOGGLY_TOKEN: '8b807190-29c8-4a5b-adfb-cfe5621fe18b', - LOGGLY_TAG: process.env.LOGGLY_TAG || 'development' + LOGGLY_TAG: process.env.LOGGLY_TAG || 'development', + OPTICS_API_KEY: process.env.OPTICS_API_KEY || 'service:icostats-dev:UZPcus12V1nCychIBLloUA' }); + +function isDebug() { + if (process.env.NODE_ENV === 'production') { + return false; + } + + if (typeof process.env.DEBUG !== 'undefined') { + return !!process.env.DEBUG; + } + + return true; +} diff --git a/src/client/app/lib/getAccounts.js b/src/client/app/lib/getAccounts.js new file mode 100644 index 0000000..d337a62 --- /dev/null +++ b/src/client/app/lib/getAccounts.js @@ -0,0 +1,21 @@ +/** + * get the coinbase if user is on a web 3 browser. + */ +import Promise from 'bluebird'; + +export default async function getAccount() { + const web3Enabled = ( + window.web3 && + window.web3.eth && + window.web3.eth.getAccounts + ); + + if (web3Enabled) { + const getAccounts = Promise.promisify(window.web3.eth.getAccounts); + const accounts = await getAccounts(); + + return accounts; + } + + return []; +} diff --git a/src/client/app/lib/identify.js b/src/client/app/lib/identify.js index 7bb42a8..586db35 100644 --- a/src/client/app/lib/identify.js +++ b/src/client/app/lib/identify.js @@ -1,20 +1,14 @@ /** - * get the coinbase if user is on a web 3 browser. + * if user is on a web 3 browser + * identify them in analytics via their coinbase */ -import Promise from 'bluebird'; + import getAccounts from './getAccounts'; -export default function identify() { - if (window.web3 && window.web3.eth && window.web3.eth.getAccounts) { - const getAccountsAsync = Promise.promisify(window.web3.eth.getAccounts); + export default async function identify() { + // take first account + const [account] = await getAccounts(); - return getAccountsAsync().then((accounts) => { - const account = accounts[0]; - - if (account) { - window.analytics.identify(account); - } - }); - } - - return false; -} + if (account) { + window.analytics.identify(account); + } + } diff --git a/src/client/exchange/actions.js b/src/client/exchange/actions.js index cfd917a..5716c5c 100644 --- a/src/client/exchange/actions.js +++ b/src/client/exchange/actions.js @@ -10,6 +10,7 @@ import type { RequestOrderStatusAction, ReceiveOrderStatusAction, ErrorValidateAddressAction } from './types'; import * as types from './constants'; +import getAddresses from '../app/lib/getAccounts'; export const initExchange = (toSymbol: string): InitExchangeAction => { @@ -125,6 +126,21 @@ export const fetchLimit = (pair: string): ThunkAction => (dispatch) => { .then(limit => dispatch(receiveLimit(limit))); }; +const requestAddresses = (): RequestAddressesAction => ({ + type: types.REQUEST_ADDRESSES +}); + +const receiveAddresses = (addresses): ReceiveAddressesAction => ({ + type: types.RECEIVE_ADDRESSES, + addresses +}); + +export const fetchAddresses = (): ThunkAction => (dispatch) => { + dispatch(requestAddresses()); + return getAddresses() + .then(addresses => dispatch(receiveAddresses(addresses))); +}; + export const setReceivingAddress = ( address: string ): SetReceivingAddressAction => ({ diff --git a/src/client/exchange/components/GetQuote.jsx b/src/client/exchange/components/GetQuote.jsx index 70f8591..7343ec5 100644 --- a/src/client/exchange/components/GetQuote.jsx +++ b/src/client/exchange/components/GetQuote.jsx @@ -9,11 +9,14 @@ import type { Action, ReduxState } from '~/exchange/types'; import CheckIcon from '~/app/components/CheckIcon'; import GetQuoteColumn from './GetQuoteColumn'; import PoweredByShapeshift from './PoweredByShapeshift'; -import { fetchValidateAddress, setReceivingAddress } from '../actions'; +import { fetchAddresses, fetchValidateAddress, setReceivingAddress } from '../actions'; type Props = { classes: Object, coins: Object, + addresses: Array, + isFetchingAddresses: boolean, + fetchAddresses: () => void, receivingAddress: string, isReceivingAddressValid: boolean, setReceivingAddress: (address: string) => void, @@ -24,7 +27,10 @@ type Props = { class GetQuote extends React.Component { props: Props; - state = {}; + + async componentWillMount() { + this.props.fetchAddresses(); + } handleChangeReceivingAddress = (event) => { event.persist(); @@ -33,13 +39,18 @@ class GetQuote extends React.Component { this.props.setReceivingAddress(address); - if (address && !this.props.isFetchingValidateAddress) { + if ( + address && + !this.props.isFetchingValidateAddress && + !this.props.isFetchingAddresses + ) { debounce(this.props.fetchValidateAddress, 300)(address, to); } } render() { const { + addresses, classes: c, coins, receivingAddress, isReceivingAddressValid } = this.props; const title = ( @@ -68,16 +79,21 @@ class GetQuote extends React.Component {
+ {addresses.map(address => ( + + ))} {isReceivingAddressValid && }
); @@ -145,6 +161,7 @@ const styles = { borderRadius: '3px', padding: '4px 0', width: '45%', + textOverflow: 'ellipsis', borderBottom: '1px solid hsl(0, 0%, 84%)', alignSelf: 'flex-end', '&.is-valid': { @@ -163,10 +180,12 @@ const styled = injectSheet(styles)(GetQuote); ============================================================================= */ const mapStateToProps = (state: {exchange: ReduxState}) => ({ to: state.exchange.to, + addresses: state.exchange.addresses, receivingAddress: state.exchange.receivingAddress, isReceivingAddressValid: state.exchange.isReceivingAddressValid }); const mapDispatchToProps = (dispatch: Dispatch) => ({ + fetchAddresses: () => dispatch(fetchAddresses()), setReceivingAddress: address => dispatch(setReceivingAddress(address)), fetchValidateAddress: (address, symbol) => dispatch(fetchValidateAddress(address, symbol)), diff --git a/src/client/exchange/constants.js b/src/client/exchange/constants.js index d90cc62..263ea84 100644 --- a/src/client/exchange/constants.js +++ b/src/client/exchange/constants.js @@ -8,6 +8,8 @@ export const REQUEST_LIMIT = 'exchange/REQUEST_LIMIT'; export const RECEIVE_LIMIT = 'exchange/RECEIVE_LIMIT'; export const REQUEST_SHIFT = 'exchange/REQUEST_SHIFT'; export const RECEIVE_SHIFT = 'exchange/RECEIVE_SHIFT'; +export const REQUEST_ADDRESSES = 'exchange/REQUEST_ADDRESSES'; +export const RECEIVE_ADDRESSES = 'exchange/RECEIVE_ADDRESSES'; export const SET_RECEIVING_ADDRESS = 'exchange/SET_RECEIVING_ADDRESS'; export const REQUEST_VALIDATE_ADDRESS = 'exchange/REQUEST_VALIDATE_ADDRESS'; export const RECEIVE_VALIDATE_ADDRESS = 'exchange/RECEIVE_VALIDATE_ADDRESS'; diff --git a/src/client/exchange/reducers.js b/src/client/exchange/reducers.js index 5ee1208..ced7e7a 100644 --- a/src/client/exchange/reducers.js +++ b/src/client/exchange/reducers.js @@ -8,6 +8,9 @@ const initialState: State = { from: 'ETH', to: 'WINGS', amount: '', + addresses: [], + receivingAddress: '', + isFetchingAddresses: false, isFetchingQuote: false, didInvalidateQuote: false, isFetchingLimit: false, @@ -78,6 +81,19 @@ const exchangeReducer = ( limit: action.limit }; + case types.REQUEST_ADDRESSES: + return { + ...state, + isFetchingAddresses: true + }; + + case types.RECEIVE_ADDRESSES: + return { + ...state, + isFetchingAddresses: false, + addresses: action.addresses + }; + case types.SET_RECEIVING_ADDRESS: return { ...state, diff --git a/src/client/exchange/types.js b/src/client/exchange/types.js index ee7a396..e17378c 100644 --- a/src/client/exchange/types.js +++ b/src/client/exchange/types.js @@ -27,6 +27,13 @@ export type ReceiveShiftAction = { type: types.RECEIVE_SHIFT, payload: { deposit: string, orderId: string } }; +export type RequestAddressesAction = { + type: types.REQUEST_ADDRESSES +}; +export type ReceiveAddressesAction = { + type: types.RECEIVE_ADDRESSES, + addresses: Array +}; export type SetReceivingAddressAction = { type: types.SET_RECEIVING_ADDRESS, address: string @@ -63,6 +70,8 @@ export type Action = | ReceiveLimitAction | RequestShiftAction | ReceiveShiftAction + | RequestAddressesAction + | ReceiveAddressesAction | SetReceivingAddressAction | RequestValidateAddressAction | ReceiveValidateAddressAction @@ -82,6 +91,8 @@ export type State = { isFetchingLimit: boolean, didInvalidateLimit: boolean, limit?: $Limit, + addresses?: Array, + isFetchingAddresses?: boolean, depositAddress?: string, receivingAddress?: string, isFetchingValidateAddress: boolean, diff --git a/src/server/app.js b/src/server/app.js index fe0ef35..e84ccd9 100755 --- a/src/server/app.js +++ b/src/server/app.js @@ -9,6 +9,7 @@ import 'winston-loggly-bulk'; import mongoose from 'mongoose'; import Promise from 'bluebird'; import { graphqlExpress, graphiqlExpress } from 'graphql-server-express'; +import OpticsAgent from 'optics-agent'; import settings from 'settings'; import schema from 'schema'; import NodeCache from 'node-cache'; @@ -61,14 +62,28 @@ winston.add(winston.transports.Loggly, { exitOnError: false }); -app.use(expressWinston.logger({ - winstonInstance: winston -})); +if (!settings.DEBUG) { + app.use(expressWinston.logger({ + winstonInstance: winston + })); +} + +/** + * Add optics to graphql + */ +OpticsAgent.configureAgent({ apiKey: settings.OPTICS_API_KEY }); +OpticsAgent.instrumentSchema(schema); +app.use(OpticsAgent.middleware()); /** * GraphQL */ -app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })); +app.use('/graphql', bodyParser.json(), graphqlExpress(req => ({ + schema, + context: { + opticsContext: OpticsAgent.context(req) + } +}))); app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' diff --git a/src/server/lib/graph-worker.js b/src/server/lib/graph-worker.js index a0fa96e..9ae1b36 100644 --- a/src/server/lib/graph-worker.js +++ b/src/server/lib/graph-worker.js @@ -90,6 +90,6 @@ function recurseOrFinish(ticker, i, didSkip = false) { } else { // Keep recursing. !didSkip && winston.info(`Fetched graph for ${ticker}`); - return recursiveFetch(tokens, i + 1); + return setTimeout(() => recursiveFetch(tokens, i + 1), 10000); } } diff --git a/src/server/schema/resolvers/icos.js b/src/server/schema/resolvers/icos.js index fb0e33a..a355d1d 100644 --- a/src/server/schema/resolvers/icos.js +++ b/src/server/schema/resolvers/icos.js @@ -3,7 +3,6 @@ import winston from 'winston'; import has from 'lodash/has'; import { normalize as normalizeICO } from 'lib/icos'; import icoData from 'lib/ico-data'; -import { fetchETHPrice, fetchBTCPrice } from 'shared/lib/exchanges/gdax'; import { cache } from 'app'; import Ticker from '~/models/ticker'; import PriceHistory from 'models/price-history'; diff --git a/tests/server/_setup.js b/tests/server/_setup.js index 8651533..8d2f0fa 100755 --- a/tests/server/_setup.js +++ b/tests/server/_setup.js @@ -1,4 +1,5 @@ /* eslint-disable */ +import 'isomorphic-fetch'; import mongoose from 'mongoose'; import Promise from 'bluebird'; import settings from 'settings';