diff --git a/flow-defs/process.js b/flow-defs/process.js index 06b5a40b..02e1b6a0 100644 --- a/flow-defs/process.js +++ b/flow-defs/process.js @@ -1,6 +1,7 @@ declare var process: { send(args: any): void, on(event: string, args: any): void, + nextTick(callback: Function): void, title: string, env: Object, } diff --git a/package.json b/package.json index 2b7c518d..7c6e070b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "axios": "^0.17.1", "bcryptjs": "^2.4.3", "bitcoinjs-lib": "^3.3.2", - "blockchain.info": "^2.11.0", "bs58check": "^2.1.1", "color": "^2.0.1", "downshift": "^1.25.0", diff --git a/src/actions/accounts.js b/src/actions/accounts.js index 8d19539f..e6cca59b 100644 --- a/src/actions/accounts.js +++ b/src/actions/accounts.js @@ -1,6 +1,5 @@ // @flow -import values from 'lodash/values' import { createAction } from 'redux-actions' import type { Dispatch } from 'redux' @@ -10,8 +9,8 @@ import db from 'helpers/db' import type { Account } from 'types/common' import type { State } from 'reducers' -import { getAccounts } from 'reducers/accounts' -import { getAddressData } from 'helpers/btc' +// import { getAccounts } from 'reducers/accounts' +// import { getAddressData } from 'helpers/btc' export type AddAccount = Account => { type: string, payload: Account } export const addAccount: AddAccount = payload => ({ @@ -25,22 +24,22 @@ export const fetchAccounts: FetchAccounts = () => ({ payload: db('accounts'), }) -const setAccountData = createAction('SET_ACCOUNT_DATA', (accountID, data) => ({ accountID, data })) +// const setAccountData = createAction('SET_ACCOUNT_DATA', (accountID, data) => ({ accountID, data })) export const syncAccount: Function = account => async (dispatch: Dispatch<*>) => { - const { address } = account - const addressData = await getAddressData(address) - dispatch(setAccountData(account.id, addressData)) + // const { address } = account + // const addressData = await getAddressData(address) + // dispatch(setAccountData(account.id, addressData)) } export const syncAccounts = () => async (dispatch: Dispatch<*>, getState: () => State) => { - const state = getState() - const accountsMap = getAccounts(state) - const accounts = values(accountsMap) - - console.log(`syncing accounts...`) - - await Promise.all(accounts.map(account => dispatch(syncAccount(account)))) - - console.log(`all accounts synced`) + // const state = getState() + // const accountsMap = getAccounts(state) + // const accounts = values(accountsMap) + // + // console.log(`syncing accounts...`) + // + // await Promise.all(accounts.map(account => dispatch(syncAccount(account)))) + // + // console.log(`all accounts synced`) } diff --git a/src/components/SideBar/index.js b/src/components/SideBar/index.js index f3ae5bd6..125d30ed 100644 --- a/src/components/SideBar/index.js +++ b/src/components/SideBar/index.js @@ -79,7 +79,7 @@ class SideBar extends PureComponent { {Object.entries(accounts).map(([id, account]: [string, any]) => ( {account.name} diff --git a/src/components/modals/AddAccount.js b/src/components/modals/AddAccount.js index dd7ed1bf..e7e29c9e 100644 --- a/src/components/modals/AddAccount.js +++ b/src/components/modals/AddAccount.js @@ -3,13 +3,15 @@ import React, { PureComponent } from 'react' import styled from 'styled-components' import { connect } from 'react-redux' +import { ipcRenderer } from 'electron' import type { MapStateToProps } from 'react-redux' -import type { Device } from 'types/common' +import type { Accounts, Device } from 'types/common' -import { sendSyncEvent } from 'renderer/events' -import { getCurrentDevice } from 'reducers/devices' import { closeModal } from 'reducers/modals' +import { getAccounts } from 'reducers/accounts' +import { getCurrentDevice } from 'reducers/devices' +import { sendEvent } from 'renderer/events' import { addAccount } from 'actions/accounts' @@ -49,12 +51,34 @@ const Steps = { ), connectDevice: () =>
Connect your Ledger
, startWallet: (props: Object) =>
Select {props.wallet.toUpperCase()} App on your Ledger
, - confirmation: (props: Object) => ( + inProgress: (props: Object) => (
- Add {props.wallet.toUpperCase()} - {props.accountName} - {props.walletAddress} ? - + In progress. + {props.progress !== null && ( +
+ Account: {props.progress.account} / Transactions: {props.progress.transactions} +
+ )}
), + listAccounts: (props: Object) => { + const accounts = Object.entries(props.accounts) + return ( +
+ {accounts.length > 0 + ? accounts.map(([index, account]: [string, any]) => ( +
+
Balance: {account.balance}
+
Transactions: {account.transactions.length}
+
+ +
+
+ )) + : 'No accounts'} +
+ ) + }, } type InputValue = { @@ -62,20 +86,23 @@ type InputValue = { wallet: string, } -type Step = 'createAccount' | 'connectDevice' | 'startWallet' | 'confirmation' +type Step = 'createAccount' | 'connectDevice' | 'inProgress' | 'startWallet' | 'listAccounts' type Props = { addAccount: Function, closeModal: Function, currentDevice: Device | null, + accounts: Accounts, } type State = { inputValue: InputValue, step: Step, - walletAddress: string, + accounts: Object, + progress: null | Object, } const mapStateToProps: MapStateToProps<*, *, *> = state => ({ + accounts: getAccounts(state), currentDevice: getCurrentDevice(state), }) @@ -89,7 +116,8 @@ const defaultState = { accountName: '', wallet: '', }, - walletAddress: '', + accounts: {}, + progress: null, step: 'createAccount', } @@ -98,6 +126,10 @@ class AddAccountModal extends PureComponent { ...defaultState, } + componentDidMount() { + ipcRenderer.on('msg', this.handleWalletRequest) + } + componentWillReceiveProps(nextProps) { const { currentDevice } = nextProps @@ -121,31 +153,21 @@ class AddAccountModal extends PureComponent { getWalletInfos() { const { inputValue } = this.state - const { currentDevice } = this.props + const { currentDevice, accounts } = this.props if (currentDevice === null) { return } - const { data: { data }, type } = sendSyncEvent('usb', 'wallet.request', { + sendEvent('usb', 'wallet.request', { path: currentDevice.path, wallet: inputValue.wallet, + currentAccounts: Object.keys(accounts), }) - - if (type === 'wallet.request.fail') { - this._timeout = setTimeout(() => this.getWalletInfos(), 1e3) - } - - if (type === 'wallet.request.success') { - this.setState({ - walletAddress: data.bitcoinAddress, - step: 'confirmation', - }) - } } getStepProps() { - const { inputValue, walletAddress, step } = this.state + const { inputValue, step, progress, accounts } = this.state const props = (predicate, props) => (predicate ? props : {}) @@ -158,26 +180,57 @@ class AddAccountModal extends PureComponent { ...props(step === 'startWallet', { wallet: inputValue.wallet, }), - ...props(step === 'confirmation', { - accountName: inputValue.accountName, - onConfirm: this.handleAddAccount, - wallet: inputValue.wallet, - walletAddress, + ...props(step === 'inProgress', { + progress, + }), + ...props(step === 'listAccounts', { + accounts, + onAddAccount: this.handleAddAccount, }), } } - handleAddAccount = () => { - const { inputValue, walletAddress } = this.state + componentWillUmount() { + ipcRenderer.removeListener('msg', this.handleWalletRequest) + clearTimeout(this._timeout) + } + + handleWalletRequest = (e, { data, type }) => { + if (type === 'wallet.request.progress') { + this.setState({ + step: 'inProgress', + progress: data, + }) + } + + if (type === 'wallet.request.fail') { + this._timeout = setTimeout(() => this.getWalletInfos(), 1e3) + } + + if (type === 'wallet.request.success') { + this.setState({ + accounts: data, + step: 'listAccounts', + }) + } + } + + handleAddAccount = index => () => { + const { inputValue, accounts } = this.state const { addAccount, closeModal } = this.props - const account = { + const { id, balance, transactions } = accounts[index] + + addAccount({ + id, name: inputValue.accountName, type: inputValue.wallet, - address: walletAddress, - } + data: { + balance, + transactions, + }, + }) - addAccount(account) closeModal('add-account') } diff --git a/src/helpers/btc.js b/src/helpers/btc.js index 4e167743..0eff1d1e 100644 --- a/src/helpers/btc.js +++ b/src/helpers/btc.js @@ -1,15 +1,11 @@ -import blockexplorer from 'blockchain.info/blockexplorer' - -const explorer = blockexplorer.usingNetwork(3) - -function computeTransaction(address) { +export function computeTransaction(addresses) { return transaction => { - const outputVal = transaction.out - .filter(o => o.addr === address) + const outputVal = transaction.outputs + .filter(o => addresses.includes(o.address)) .reduce((acc, cur) => acc + cur.value, 0) const inputVal = transaction.inputs - .filter(i => i.prev_out.addr === address) - .reduce((acc, cur) => acc + cur.prev_out.value, 0) + .filter(i => addresses.includes(i.address)) + .reduce((acc, cur) => acc + cur.value, 0) const balance = outputVal - inputVal return { ...transaction, @@ -17,13 +13,3 @@ function computeTransaction(address) { } } } - -export async function getAddressData(address) { - const addressData = await explorer.getAddress(address) - const unifiedData = { - address, - balance: addressData.final_balance, - transactions: addressData.txs.map(computeTransaction(address)), - } - return unifiedData -} diff --git a/src/internals/usb/wallet/getAddresses.js b/src/internals/usb/wallet/accounts.js similarity index 50% rename from src/internals/usb/wallet/getAddresses.js rename to src/internals/usb/wallet/accounts.js index 6978bfc8..802515b4 100644 --- a/src/internals/usb/wallet/getAddresses.js +++ b/src/internals/usb/wallet/accounts.js @@ -5,7 +5,9 @@ import bitcoin from 'bitcoinjs-lib' import bs58check from 'bs58check' import Btc from '@ledgerhq/hw-app-btc' -const networks = [ +import { computeTransaction } from 'helpers/btc' + +export const networks = [ { ...bitcoin.networks.bitcoin, family: 1, @@ -36,7 +38,7 @@ function parseHexString(str) { return result } -function createXPUB({ depth, fingerprint, childnum, chainCode, publicKey, network }) { +function createXpub({ depth, fingerprint, childnum, chainCode, publicKey, network }) { return [ network.toString(16).padStart(8, 0), depth.toString(16).padStart(2, 0), @@ -49,7 +51,8 @@ function createXPUB({ depth, fingerprint, childnum, chainCode, publicKey, networ function encodeBase58Check(vchIn) { vchIn = parseHexString(vchIn) - return bs58check.encode(new Uint8Array(vchIn)) + + return bs58check.encode(Buffer.from(vchIn)) } function getPath({ coin, account, segwit }) { @@ -78,10 +81,52 @@ function getTransactions(addresses) { ) } -export default async transport => { - const coin = 1 - const account = 0 - const segwit = true +export async function getAccount({ hdnode, segwit, network }) { + const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10) + + let transactions = [] + + const nextPath = start => { + const count = 20 + const getAddress = path => getPublicAddress(hdnode, path, script, segwit) + + return Promise.all( + Array.from(Array(count).keys()).map(v => + Promise.all([ + getAddress(`0/${v + start}`), // external chain + getAddress(`1/${v + start}`), // internal chain + ]), + ), + ).then(async results => { + const currentAddresses = results.reduce((result, v) => [...result, ...v], []) + + const { data: { txs } } = await getTransactions(currentAddresses) + + transactions = [...transactions, ...txs.map(computeTransaction(currentAddresses))] + + if (txs.length > 0) { + return nextPath(start + (count - 1)) + } + + return { + balance: transactions.reduce((result, v) => { + result += v.balance + return result + }, 0), + transactions, + } + }) + } + + return nextPath(0) +} + +export function getHDNode({ xpub58, network }) { + return bitcoin.HDNode.fromBase58(xpub58, network) +} + +export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit = true }) => { + const btc = new Btc(transport) const network = networks[coin] @@ -91,8 +136,6 @@ export default async transport => { await transport.exchange(`e014000005${p2pkh}${p2sh}${fam.substr(-2)}`, [0x9000]) - const btc = new Btc(transport) - const getPublicKey = path => btc.getWalletPublicKey(path) let result = bitcoin.crypto.sha256( @@ -102,47 +145,54 @@ export default async transport => { ) result = bitcoin.crypto.ripemd160(result) - const fingerprint = ((result[0] << 24) | (result[1] << 16) | (result[2] << 8) | result[3]) >>> 0 + onProgress(null) - const { publicKey, chainCode } = await getPublicKey(getPath({ segwit, coin, account })) - const compressPublicKey = getCompressPublicKey(publicKey) + const fingerprint = ((result[0] << 24) | (result[1] << 16) | (result[2] << 8) | result[3]) >>> 0 - const childnum = (0x80000000 | account) >>> 0 - const xpub = createXPUB({ - depth: 3, - fingerprint, - childnum, - chainCode, - publicKey: compressPublicKey, - network: network.bip32.public, - }) + const getXpub58ByAccount = async ({ account, network }) => { + const { publicKey, chainCode } = await getPublicKey(getPath({ segwit, coin, account })) + const compressPublicKey = getCompressPublicKey(publicKey) - const xpub58 = encodeBase58Check(xpub) + const childnum = (0x80000000 | account) >>> 0 - const hdnode = bitcoin.HDNode.fromBase58(xpub58, network) + const xpub = createXpub({ + depth: 3, + fingerprint, + childnum, + chainCode, + publicKey: compressPublicKey, + network: network.bip32.public, + }) - const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10) + return encodeBase58Check(xpub) + } - const nextPath = async i => { - if (i <= 0x7fffffff) { - for (let j = 0; j < 2; j++) { - const path = `${j}/${i}` + const getAllAccounts = async (currentAccount = 0, accounts = {}) => { + const xpub58 = await getXpub58ByAccount({ account: currentAccount, network }) - const address = getPublicAddress(hdnode, path, script, segwit) - console.log('address', address) + if (currentAccounts.includes(xpub58)) { + return getAllAccounts(currentAccount + 1, accounts) // skip existing account + } - const { data: { txs } } = await getTransactions(address) // eslint-disable-line no-await-in-loop + const hdnode = getHDNode({ xpub58, network }) + const { transactions, balance } = await getAccount({ hdnode, network, segwit }) - console.log('txs', txs.length) + onProgress({ + account: currentAccount, + transactions: transactions.length, + }) - if (j === 1 && i < 10) { - nextPath(++i) - } + if (transactions.length > 0) { + accounts[currentAccount] = { + id: xpub58, + balance, + transactions, } - } else { - console.log('meeeh') + return getAllAccounts(currentAccount + 1, accounts) } + + return accounts } - nextPath(0) + return getAllAccounts() } diff --git a/src/internals/usb/wallet/index.js b/src/internals/usb/wallet/index.js index aee8fe2a..f465ce31 100644 --- a/src/internals/usb/wallet/index.js +++ b/src/internals/usb/wallet/index.js @@ -1,24 +1,39 @@ // @flow import CommNodeHid from '@ledgerhq/hw-transport-node-hid' -import getAddresses from './getAddresses' -async function getWallet(path, wallet) { +import getAllAccounts from './accounts' + +async function getAllAccountsByWallet({ path, wallet, currentAccounts, onProgress }) { const transport = await CommNodeHid.open(path) - console.log('getWallet', path) + if (wallet === 'btc') { - await getAddresses(transport) + return getAllAccounts({ transport, currentAccounts, onProgress }) } + throw new Error('invalid wallet') } export default (sendEvent: Function) => ({ - request: async ({ path, wallet }: { path: string, wallet: string }) => { + request: async ({ + path, + wallet, + currentAccounts, + }: { + path: string, + wallet: string, + currentAccounts: Array<*>, + }) => { try { - const data = await getWallet(path, wallet) - sendEvent('wallet.request.success', { path, wallet, data }) + const data = await getAllAccountsByWallet({ + path, + wallet, + currentAccounts, + onProgress: progress => sendEvent('wallet.request.progress', progress, { kill: false }), + }) + sendEvent('wallet.request.success', data) } catch (err) { - sendEvent('wallet.request.fail', { path, wallet, err: err.stack || err }) + sendEvent('wallet.request.fail', err.stack || err) } }, }) diff --git a/src/reducers/accounts.js b/src/reducers/accounts.js index f7b6634b..c2a3febe 100644 --- a/src/reducers/accounts.js +++ b/src/reducers/accounts.js @@ -1,7 +1,6 @@ // @flow import { handleActions } from 'redux-actions' -import shortid from 'shortid' import get from 'lodash/get' import type { State } from 'reducers' @@ -12,17 +11,12 @@ export type AccountsState = Accounts const state: AccountsState = {} const handlers: Object = { - ADD_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) => { - const id = shortid.generate() - - return { - ...state, - [id]: { - id, - ...account, - }, - } - }, + ADD_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) => ({ + ...state, + [account.id]: { + ...account, + }, + }), FETCH_ACCOUNTS: (state: AccountsState, { payload: accounts }: { payload: Accounts }) => accounts, SET_ACCOUNT_DATA: ( state: AccountsState, diff --git a/src/types/common.js b/src/types/common.js index 1e356976..d1fe6d13 100644 --- a/src/types/common.js +++ b/src/types/common.js @@ -18,7 +18,6 @@ export type Transaction = { // -------------------- Accounts export type AccountData = { - address: string, balance: number, transactions: Array, } @@ -27,7 +26,6 @@ export type Account = { id: string, name: string, type: string, - address: string, data?: AccountData, } diff --git a/yarn.lock b/yarn.lock index 84989a3e..3e8a120f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1787,28 +1787,12 @@ block-stream@*: dependencies: inherits "~2.0.0" -blockchain.info@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/blockchain.info/-/blockchain.info-2.11.0.tgz#63b46617e194164d377e183e6c667d3ef38ad5b6" - dependencies: - q "^1.4.1" - request-promise "^0.4.3" - url-join "0.0.1" - url-parse "^1.0.5" - url-pattern "^0.10.2" - optionalDependencies: - ws "^1.1.2" - bluebird-lst@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.5.tgz#bebc83026b7e92a72871a3dc599e219cbfb002a9" dependencies: bluebird "^3.5.1" -bluebird@^2.3: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -2195,7 +2179,7 @@ chalk@0.5.1: strip-ansi "^0.3.0" supports-color "^0.2.0" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -5681,7 +5665,7 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^3.10.0, lodash@^3.10.1: +lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" @@ -6378,10 +6362,6 @@ optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - ora@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" @@ -7151,7 +7131,7 @@ pushdata-bitcoin@^1.0.1: dependencies: bitcoin-ops "^1.3.0" -q@^1.1.2, q@^1.4.1: +q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -7772,15 +7752,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request-promise@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-0.4.3.tgz#3c8ddc82f06f8908d720aede1d6794258e22121c" - dependencies: - bluebird "^2.3" - chalk "^1.1.0" - lodash "^3.10.0" - request "^2.34" - request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -7808,7 +7779,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.34, request@^2.45.0, request@^2.81.0, request@^2.83.0: +request@^2.45.0, request@^2.81.0, request@^2.83.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -8928,10 +8899,6 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -9029,10 +8996,6 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url-join@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" - url-loader@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" @@ -9054,17 +9017,13 @@ url-parse@1.0.x: querystringify "0.0.x" requires-port "1.0.x" -url-parse@^1.0.5, url-parse@^1.1.8: +url-parse@^1.1.8: version "1.2.0" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" dependencies: querystringify "~1.0.0" requires-port "~1.0.0" -url-pattern@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/url-pattern/-/url-pattern-0.10.2.tgz#e9f07104982b72312db4473dd86a527b580015da" - url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -9384,13 +9343,6 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" -ws@^1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" - dependencies: - options ">=0.0.5" - ultron "1.0.x" - xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"