mirror of
https://github.com/trezor/trezor-wallet
synced 2025-01-12 09:00:58 +00:00
Merge branch 'master' of github.com:satoshilabs/trezor-wallet
This commit is contained in:
commit
8e7dfb1b83
@ -6,7 +6,7 @@
|
||||
"bin": {
|
||||
"flow": "./node_modules/flow-bin"
|
||||
},
|
||||
"license": "LGPL-3.0+",
|
||||
"license": "T-RSL",
|
||||
"scripts": {
|
||||
"dev": "npx webpack-dev-server --config webpack/dev.babel.js",
|
||||
"dev:local": "npx webpack-dev-server --config webpack/local.babel.js",
|
||||
|
@ -7,11 +7,23 @@ import * as TOKEN from 'actions/constants/token';
|
||||
import * as DISCOVERY from 'actions/constants/discovery';
|
||||
import * as STORAGE from 'actions/constants/localStorage';
|
||||
import * as PENDING from 'actions/constants/pendingTx';
|
||||
import * as WALLET from 'actions/constants/wallet';
|
||||
import { httpRequest } from 'utils/networkUtils';
|
||||
import * as buildUtils from 'utils/build';
|
||||
|
||||
import { findAccountTokens } from 'reducers/TokensReducer';
|
||||
import type { Account } from 'reducers/AccountsReducer';
|
||||
import type { Token } from 'reducers/TokensReducer';
|
||||
import type { PendingTx } from 'reducers/PendingTxReducer';
|
||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||
|
||||
|
||||
import type {
|
||||
ThunkAction, AsyncAction, /* GetState, */ Dispatch,
|
||||
TrezorDevice,
|
||||
ThunkAction,
|
||||
AsyncAction,
|
||||
GetState,
|
||||
Dispatch,
|
||||
} from 'flowtype';
|
||||
import type { Config, Network, TokensCollection } from 'reducers/LocalStorageReducer';
|
||||
|
||||
@ -31,7 +43,7 @@ export type StorageAction = {
|
||||
error: string,
|
||||
};
|
||||
|
||||
export const get = (key: string): ?string => {
|
||||
const get = (key: string): ?string => {
|
||||
try {
|
||||
return window.localStorage.getItem(key);
|
||||
} catch (error) {
|
||||
@ -40,168 +52,212 @@ export const get = (key: string): ?string => {
|
||||
}
|
||||
};
|
||||
|
||||
export function update(event: StorageEvent): AsyncAction {
|
||||
return async (dispatch: Dispatch/* , getState: GetState */): Promise<void> => {
|
||||
if (!event.newValue) return;
|
||||
const set = (key: string, value: string | boolean): void => {
|
||||
try {
|
||||
window.localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
console.error(`Local Storage ERROR: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
if (event.key === 'devices') {
|
||||
// check if device was added/ removed
|
||||
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
|
||||
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
|
||||
// https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js
|
||||
// or
|
||||
// https://www.npmjs.com/package/redux-react-session
|
||||
|
||||
// if (newDevices.length !== myDevices.length) {
|
||||
// const diff = myDevices.filter(d => newDevices.indexOf(d) < 0)
|
||||
// console.warn("DEV LIST CHANGED!", newDevices.length, myDevices.length, diff)
|
||||
// // check if difference is caused by local device which is not saved
|
||||
// // or device which was saved in other tab
|
||||
// }
|
||||
const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): Array<Account> => devices.reduce((arr, dev) => arr.concat(accounts.filter(a => a.deviceState === dev.state)), []);
|
||||
|
||||
// const diff = oldDevices.filter(d => newDevices.indexOf())
|
||||
}
|
||||
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> => accounts.reduce((arr, account) => arr.concat(findAccountTokens(tokens, account)), []);
|
||||
|
||||
if (event.key === 'accounts') {
|
||||
dispatch({
|
||||
type: ACCOUNT.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
const findDiscovery = (devices: Array<TrezorDevice>, discovery: Array<Discovery>): Array<Discovery> => devices.reduce((arr, dev) => arr.concat(discovery.filter(a => a.deviceState === dev.state && a.publicKey.length > 0)), []);
|
||||
|
||||
if (event.key === 'tokens') {
|
||||
dispatch({
|
||||
type: TOKEN.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
const findPendingTxs = (accounts: Array<Account>, pending: Array<PendingTx>): Array<PendingTx> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.address === account.address && p.network === account.network)), []);
|
||||
|
||||
if (event.key === 'pending') {
|
||||
dispatch({
|
||||
type: PENDING.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
|
||||
const accounts: Array<Account> = findAccounts(devices, getState().accounts);
|
||||
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
|
||||
const pending: Array<PendingTx> = findPendingTxs(accounts, getState().pending);
|
||||
const discovery: Array<Discovery> = findDiscovery(devices, getState().discovery);
|
||||
|
||||
if (event.key === 'discovery') {
|
||||
dispatch({
|
||||
type: DISCOVERY.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
// save devices
|
||||
set('devices', JSON.stringify(devices));
|
||||
|
||||
// save already preloaded accounts
|
||||
set('accounts', JSON.stringify(accounts));
|
||||
|
||||
// save discovery state
|
||||
set('discovery', JSON.stringify(discovery));
|
||||
|
||||
// tokens
|
||||
set('tokens', JSON.stringify(tokens));
|
||||
|
||||
// pending transactions
|
||||
set('pending', JSON.stringify(pending));
|
||||
};
|
||||
|
||||
export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch): void => {
|
||||
if (!event.newValue) return;
|
||||
|
||||
if (event.key === 'devices') {
|
||||
// check if device was added/ removed
|
||||
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
|
||||
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
|
||||
|
||||
// if (newDevices.length !== myDevices.length) {
|
||||
// const diff = myDevices.filter(d => newDevices.indexOf(d) < 0)
|
||||
// console.warn("DEV LIST CHANGED!", newDevices.length, myDevices.length, diff)
|
||||
// // check if difference is caused by local device which is not saved
|
||||
// // or device which was saved in other tab
|
||||
// }
|
||||
|
||||
// const diff = oldDevices.filter(d => newDevices.indexOf())
|
||||
}
|
||||
|
||||
if (event.key === 'accounts') {
|
||||
dispatch({
|
||||
type: ACCOUNT.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
|
||||
if (event.key === 'tokens') {
|
||||
dispatch({
|
||||
type: TOKEN.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
|
||||
if (event.key === 'pending') {
|
||||
dispatch({
|
||||
type: PENDING.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
|
||||
if (event.key === 'discovery') {
|
||||
dispatch({
|
||||
type: DISCOVERY.FROM_STORAGE,
|
||||
payload: JSON.parse(event.newValue),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const VERSION: string = '1';
|
||||
|
||||
export function loadTokensFromJSON(): AsyncAction {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
if (typeof window.localStorage === 'undefined') return;
|
||||
const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
|
||||
if (typeof window.localStorage === 'undefined') return;
|
||||
|
||||
try {
|
||||
const config: Config = await httpRequest(AppConfigJSON, 'json');
|
||||
try {
|
||||
const config: Config = await httpRequest(AppConfigJSON, 'json');
|
||||
|
||||
if (!buildUtils.isDev()) {
|
||||
const index = config.networks.findIndex(c => c.shortcut === 'trop');
|
||||
delete config.networks[index];
|
||||
}
|
||||
|
||||
const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json');
|
||||
|
||||
window.addEventListener('storage', (event) => {
|
||||
dispatch(update(event));
|
||||
});
|
||||
|
||||
// validate version
|
||||
const version: ?string = get('version');
|
||||
if (version !== VERSION) {
|
||||
window.localStorage.clear();
|
||||
dispatch(save('version', VERSION));
|
||||
}
|
||||
|
||||
// load tokens
|
||||
const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => {
|
||||
const collection: TokensCollection = await promise;
|
||||
const json = await httpRequest(network.tokens, 'json');
|
||||
collection[network.shortcut] = json;
|
||||
return collection;
|
||||
}, Promise.resolve({}));
|
||||
|
||||
const devices: ?string = get('devices');
|
||||
if (devices) {
|
||||
dispatch({
|
||||
type: CONNECT.DEVICE_FROM_STORAGE,
|
||||
payload: JSON.parse(devices),
|
||||
});
|
||||
}
|
||||
|
||||
const accounts: ?string = get('accounts');
|
||||
if (accounts) {
|
||||
dispatch({
|
||||
type: ACCOUNT.FROM_STORAGE,
|
||||
payload: JSON.parse(accounts),
|
||||
});
|
||||
}
|
||||
|
||||
const userTokens: ?string = get('tokens');
|
||||
if (userTokens) {
|
||||
dispatch({
|
||||
type: TOKEN.FROM_STORAGE,
|
||||
payload: JSON.parse(userTokens),
|
||||
});
|
||||
}
|
||||
|
||||
const pending: ?string = get('pending');
|
||||
if (pending) {
|
||||
dispatch({
|
||||
type: PENDING.FROM_STORAGE,
|
||||
payload: JSON.parse(pending),
|
||||
});
|
||||
}
|
||||
|
||||
const discovery: ?string = get('discovery');
|
||||
if (discovery) {
|
||||
dispatch({
|
||||
type: DISCOVERY.FROM_STORAGE,
|
||||
payload: JSON.parse(discovery),
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: STORAGE.READY,
|
||||
config,
|
||||
tokens,
|
||||
ERC20Abi,
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: STORAGE.ERROR,
|
||||
error,
|
||||
});
|
||||
if (!buildUtils.isDev()) {
|
||||
const index = config.networks.findIndex(c => c.shortcut === 'trop');
|
||||
delete config.networks[index];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const loadData = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
// check if local storage is available
|
||||
// let available: boolean = true;
|
||||
// if (typeof window.localStorage === 'undefined') {
|
||||
// available = false;
|
||||
// } else {
|
||||
// try {
|
||||
// window.localStorage.setItem('ethereum_wallet', true);
|
||||
// } catch (error) {
|
||||
// available = false;
|
||||
// }
|
||||
// }
|
||||
const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json');
|
||||
|
||||
dispatch(loadTokensFromJSON());
|
||||
window.addEventListener('storage', (event) => {
|
||||
dispatch(update(event));
|
||||
});
|
||||
|
||||
// validate version
|
||||
const version: ?string = get('version');
|
||||
if (version !== VERSION) {
|
||||
try {
|
||||
window.localStorage.clear();
|
||||
window.sessionStorage.clear();
|
||||
} catch (error) {
|
||||
// empty
|
||||
}
|
||||
set('version', VERSION);
|
||||
}
|
||||
|
||||
// load tokens
|
||||
const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => {
|
||||
const collection: TokensCollection = await promise;
|
||||
const json = await httpRequest(network.tokens, 'json');
|
||||
collection[network.shortcut] = json;
|
||||
return collection;
|
||||
}, Promise.resolve({}));
|
||||
|
||||
dispatch({
|
||||
type: STORAGE.READY,
|
||||
config,
|
||||
tokens,
|
||||
ERC20Abi,
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: STORAGE.ERROR,
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const save = (key: string, value: string): ThunkAction => (): void => {
|
||||
if (typeof window.localStorage !== 'undefined') {
|
||||
try {
|
||||
window.localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
// available = false;
|
||||
console.error(`Local Storage ERROR: ${error}`);
|
||||
|
||||
const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
const devices: ?string = get('devices');
|
||||
if (devices) {
|
||||
dispatch({
|
||||
type: CONNECT.DEVICE_FROM_STORAGE,
|
||||
payload: JSON.parse(devices),
|
||||
});
|
||||
}
|
||||
|
||||
const accounts: ?string = get('accounts');
|
||||
if (accounts) {
|
||||
dispatch({
|
||||
type: ACCOUNT.FROM_STORAGE,
|
||||
payload: JSON.parse(accounts),
|
||||
});
|
||||
}
|
||||
|
||||
const userTokens: ?string = get('tokens');
|
||||
if (userTokens) {
|
||||
dispatch({
|
||||
type: TOKEN.FROM_STORAGE,
|
||||
payload: JSON.parse(userTokens),
|
||||
});
|
||||
}
|
||||
|
||||
const pending: ?string = get('pending');
|
||||
if (pending) {
|
||||
dispatch({
|
||||
type: PENDING.FROM_STORAGE,
|
||||
payload: JSON.parse(pending),
|
||||
});
|
||||
}
|
||||
|
||||
const discovery: ?string = get('discovery');
|
||||
if (discovery) {
|
||||
dispatch({
|
||||
type: DISCOVERY.FROM_STORAGE,
|
||||
payload: JSON.parse(discovery),
|
||||
});
|
||||
}
|
||||
|
||||
if (buildUtils.isDev() || buildUtils.isBeta()) {
|
||||
const betaModal = get('/betaModalPrivacy');
|
||||
if (!betaModal) {
|
||||
dispatch({
|
||||
type: WALLET.SHOW_BETA_DISCLAIMER,
|
||||
show: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||
dispatch(loadStorageData());
|
||||
|
||||
// stop loading resources and wait for user action
|
||||
if (!getState().wallet.showBetaDisclaimer) {
|
||||
dispatch(loadJSON());
|
||||
}
|
||||
};
|
||||
|
||||
export const hideBetaDisclaimer = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
set('/betaModalPrivacy', true);
|
||||
dispatch(loadJSON());
|
||||
};
|
||||
|
@ -119,7 +119,6 @@ export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean, state
|
||||
};
|
||||
|
||||
export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dispatch: Dispatch): void => {
|
||||
console.warn('OPEN', id, url);
|
||||
dispatch({
|
||||
type: MODAL.OPEN_EXTERNAL_WALLET,
|
||||
id,
|
||||
@ -127,7 +126,6 @@ export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dis
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
onPinSubmit,
|
||||
onPassphraseSubmit,
|
||||
|
@ -153,7 +153,7 @@ export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dis
|
||||
const shouldBeLandingPage = getState().devices.length < 1 || !getState().wallet.ready || getState().connect.error !== null;
|
||||
const landingPageUrl = dispatch(isLandingPageUrl(requestedUrl));
|
||||
if (shouldBeLandingPage) {
|
||||
const landingPageRoute = dispatch(isLandingPageUrl(requestedUrl, true));
|
||||
const landingPageRoute = dispatch(isLandingPageUrl(requestedUrl, getState().wallet.ready));
|
||||
return !landingPageRoute ? '/' : requestedUrl;
|
||||
}
|
||||
|
||||
|
@ -56,14 +56,16 @@ export const setBalance = (tokenAddress: string, ethAddress: string, balance: st
|
||||
const newState: Array<Token> = [...getState().tokens];
|
||||
const token: ?Token = newState.find(t => t.address === tokenAddress && t.ethAddress === ethAddress);
|
||||
if (token) {
|
||||
token.loaded = true;
|
||||
token.balance = balance;
|
||||
const others = newState.filter(t => t !== token);
|
||||
dispatch({
|
||||
type: TOKEN.SET_BALANCE,
|
||||
payload: others.concat([{
|
||||
...token,
|
||||
loaded: true,
|
||||
balance,
|
||||
}]),
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TOKEN.SET_BALANCE,
|
||||
payload: newState,
|
||||
});
|
||||
};
|
||||
|
||||
export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
|
||||
|
@ -38,6 +38,8 @@ export type WalletAction = {
|
||||
} | {
|
||||
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
|
||||
devices: Array<TrezorDevice>
|
||||
} | {
|
||||
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER,
|
||||
}
|
||||
|
||||
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
@ -51,6 +53,10 @@ export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
window.addEventListener('offline', updateOnlineStatus);
|
||||
};
|
||||
|
||||
export const hideBetaDisclaimer = (): WalletAction => ({
|
||||
type: WALLET.HIDE_BETA_DISCLAIMER,
|
||||
});
|
||||
|
||||
export const toggleDeviceDropdown = (opened: boolean): WalletAction => ({
|
||||
type: WALLET.TOGGLE_DEVICE_DROPDOWN,
|
||||
opened,
|
||||
|
@ -6,5 +6,7 @@ export const ONLINE_STATUS: 'wallet__online_status' = 'wallet__online_status';
|
||||
|
||||
export const SET_SELECTED_DEVICE: 'wallet__set_selected_device' = 'wallet__set_selected_device';
|
||||
export const UPDATE_SELECTED_DEVICE: 'wallet__update_selected_device' = 'wallet__update_selected_device';
|
||||
export const SHOW_BETA_DISCLAIMER: 'wallet__show_beta_disclaimer' = 'wallet__show_beta_disclaimer';
|
||||
export const HIDE_BETA_DISCLAIMER: 'wallet__hide_beta_disclaimer' = 'wallet__hide_beta_disclaimer';
|
||||
|
||||
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' = 'wallet__clear_unavailable_device_data';
|
@ -2,47 +2,47 @@ export default [
|
||||
{
|
||||
id: 'btc',
|
||||
coinName: 'Bitcoin',
|
||||
url: 'https://wallet.trezor.io/#/?coin=btc',
|
||||
url: '../#/?coin=btc',
|
||||
},
|
||||
{
|
||||
id: 'bch',
|
||||
coinName: 'Bitcoin Cash',
|
||||
url: 'https://wallet.trezor.io/#/?coin=bch',
|
||||
url: '../#/?coin=bch',
|
||||
},
|
||||
{
|
||||
id: 'btg',
|
||||
coinName: 'Bitcoin Gold',
|
||||
url: 'https://wallet.trezor.io/#/?coin=btg',
|
||||
url: '../#/?coin=btg',
|
||||
},
|
||||
{
|
||||
id: 'dash',
|
||||
coinName: 'Dash',
|
||||
url: 'https://wallet.trezor.io/#/?coin=dash',
|
||||
url: '../#/?coin=dash',
|
||||
},
|
||||
{
|
||||
id: 'doge',
|
||||
coinName: 'Dogecoin',
|
||||
url: 'https://wallet.trezor.io/#/?coin=doge',
|
||||
url: '../#/?coin=doge',
|
||||
},
|
||||
{
|
||||
id: 'ltc',
|
||||
coinName: 'Litecoin',
|
||||
url: 'https://wallet.trezor.io/#/?coin=ltc',
|
||||
url: '../#/?coin=ltc',
|
||||
},
|
||||
{
|
||||
id: 'nmc',
|
||||
coinName: 'Namecoin',
|
||||
url: 'https://wallet.trezor.io/#/?coin=nmc',
|
||||
url: '../#/?coin=nmc',
|
||||
},
|
||||
{
|
||||
id: 'vtc',
|
||||
coinName: 'Vertcoin',
|
||||
url: 'https://wallet.trezor.io/#/?coin=vtc',
|
||||
url: '../#/?coin=vtc',
|
||||
},
|
||||
{
|
||||
id: 'zec',
|
||||
coinName: 'Zcash',
|
||||
url: 'https://wallet.trezor.io/#/?coin=zec',
|
||||
url: '../#/?coin=zec',
|
||||
},
|
||||
{
|
||||
id: 'xem',
|
||||
|
@ -7,13 +7,13 @@ import * as MODAL from 'actions/constants/modal';
|
||||
import * as WALLET from 'actions/constants/wallet';
|
||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||
|
||||
|
||||
import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
|
||||
|
||||
type State = {
|
||||
ready: boolean;
|
||||
online: boolean;
|
||||
dropdownOpened: boolean;
|
||||
showBetaDisclaimer: boolean;
|
||||
initialParams: ?RouterLocationState;
|
||||
initialPathname: ?string;
|
||||
disconnectRequest: ?TrezorDevice;
|
||||
@ -24,6 +24,7 @@ const initialState: State = {
|
||||
ready: false,
|
||||
online: navigator.onLine,
|
||||
dropdownOpened: false,
|
||||
showBetaDisclaimer: false,
|
||||
initialParams: null,
|
||||
initialPathname: null,
|
||||
disconnectRequest: null,
|
||||
@ -86,6 +87,17 @@ export default function wallet(state: State = initialState, action: Action): Sta
|
||||
selectedDevice: action.device,
|
||||
};
|
||||
|
||||
case WALLET.SHOW_BETA_DISCLAIMER:
|
||||
return {
|
||||
...state,
|
||||
showBetaDisclaimer: true,
|
||||
};
|
||||
case WALLET.HIDE_BETA_DISCLAIMER:
|
||||
return {
|
||||
...state,
|
||||
showBetaDisclaimer: false,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* @flow */
|
||||
import { DEVICE } from 'trezor-connect';
|
||||
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
||||
// import * as WalletActions from 'actions/WalletActions';
|
||||
|
||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||
import * as TOKEN from 'actions/constants/token';
|
||||
@ -9,95 +8,45 @@ import * as ACCOUNT from 'actions/constants/account';
|
||||
import * as DISCOVERY from 'actions/constants/discovery';
|
||||
import * as SEND from 'actions/constants/send';
|
||||
import * as PENDING from 'actions/constants/pendingTx';
|
||||
import { findAccountTokens } from 'reducers/TokensReducer';
|
||||
import * as WALLET from 'actions/constants/wallet';
|
||||
|
||||
|
||||
import type {
|
||||
Middleware,
|
||||
MiddlewareAPI,
|
||||
MiddlewareDispatch,
|
||||
Dispatch,
|
||||
Action,
|
||||
GetState,
|
||||
TrezorDevice,
|
||||
} from 'flowtype';
|
||||
|
||||
import type { Account } from 'reducers/AccountsReducer';
|
||||
import type { Token } from 'reducers/TokensReducer';
|
||||
import type { PendingTx } from 'reducers/PendingTxReducer';
|
||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||
|
||||
|
||||
// https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js
|
||||
// or
|
||||
// https://www.npmjs.com/package/redux-react-session
|
||||
|
||||
const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): Array<Account> => devices.reduce((arr, dev) => arr.concat(accounts.filter(a => a.deviceState === dev.state)), []);
|
||||
|
||||
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> => accounts.reduce((arr, account) => arr.concat(findAccountTokens(tokens, account)), []);
|
||||
|
||||
const findDiscovery = (devices: Array<TrezorDevice>, discovery: Array<Discovery>): Array<Discovery> => devices.reduce((arr, dev) => arr.concat(discovery.filter(a => a.deviceState === dev.state && a.publicKey.length > 0)), []);
|
||||
|
||||
const findPendingTxs = (accounts: Array<Account>, pending: Array<PendingTx>): Array<PendingTx> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.address === account.address && p.network === account.network)), []);
|
||||
|
||||
const save = (dispatch: Dispatch, getState: GetState): void => {
|
||||
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
|
||||
const accounts: Array<Account> = findAccounts(devices, getState().accounts);
|
||||
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
|
||||
const pending: Array<PendingTx> = findPendingTxs(accounts, getState().pending);
|
||||
const discovery: Array<Discovery> = findDiscovery(devices, getState().discovery);
|
||||
|
||||
// save devices
|
||||
dispatch(LocalStorageActions.save('devices', JSON.stringify(devices)));
|
||||
|
||||
// save already preloaded accounts
|
||||
dispatch(LocalStorageActions.save('accounts', JSON.stringify(accounts)));
|
||||
|
||||
// save discovery state
|
||||
dispatch(LocalStorageActions.save('discovery', JSON.stringify(discovery)));
|
||||
|
||||
// tokens
|
||||
dispatch(LocalStorageActions.save('tokens', JSON.stringify(tokens)));
|
||||
|
||||
// pending transactions
|
||||
dispatch(LocalStorageActions.save('pending', JSON.stringify(pending)));
|
||||
};
|
||||
|
||||
|
||||
const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||
// Application live cycle starts here
|
||||
// if (action.type === LOCATION_CHANGE) {
|
||||
// const { location } = api.getState().router;
|
||||
// if (!location) {
|
||||
// api.dispatch( WalletActions.init() );
|
||||
// // load data from config.json and local storage
|
||||
// api.dispatch( LocalStorageActions.loadData() );
|
||||
// }
|
||||
// }
|
||||
|
||||
// pass action
|
||||
next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case WALLET.HIDE_BETA_DISCLAIMER:
|
||||
api.dispatch(LocalStorageActions.hideBetaDisclaimer());
|
||||
break;
|
||||
// first time saving
|
||||
case CONNECT.REMEMBER:
|
||||
save(api.dispatch, api.getState);
|
||||
api.dispatch(LocalStorageActions.save());
|
||||
break;
|
||||
|
||||
case TOKEN.ADD:
|
||||
case TOKEN.REMOVE:
|
||||
case TOKEN.SET_BALANCE:
|
||||
save(api.dispatch, api.getState);
|
||||
api.dispatch(LocalStorageActions.save());
|
||||
break;
|
||||
|
||||
case ACCOUNT.CREATE:
|
||||
case ACCOUNT.SET_BALANCE:
|
||||
case ACCOUNT.SET_NONCE:
|
||||
save(api.dispatch, api.getState);
|
||||
api.dispatch(LocalStorageActions.save());
|
||||
break;
|
||||
|
||||
case DISCOVERY.START:
|
||||
case DISCOVERY.STOP:
|
||||
case DISCOVERY.COMPLETE:
|
||||
save(api.dispatch, api.getState);
|
||||
api.dispatch(LocalStorageActions.save());
|
||||
break;
|
||||
|
||||
case CONNECT.FORGET:
|
||||
@ -107,13 +56,13 @@ const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: Middlewar
|
||||
case DEVICE.CHANGED:
|
||||
case DEVICE.DISCONNECT:
|
||||
case CONNECT.AUTH_DEVICE:
|
||||
save(api.dispatch, api.getState);
|
||||
api.dispatch(LocalStorageActions.save());
|
||||
break;
|
||||
|
||||
case SEND.TX_COMPLETE:
|
||||
case PENDING.TX_RESOLVED:
|
||||
case PENDING.TX_REJECTED:
|
||||
save(api.dispatch, api.getState);
|
||||
api.dispatch(LocalStorageActions.save());
|
||||
break;
|
||||
|
||||
default:
|
||||
|
41
src/utils/__tests__/__snapshots__/notification.test.js.snap
Normal file
41
src/utils/__tests__/__snapshots__/notification.test.js.snap
Normal file
@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`device utils get icon 1`] = `
|
||||
Array [
|
||||
"M693.024 330.944c-99.968-99.936-262.080-99.936-362.048 0s-99.968 262.112 0 362.080c99.968 100 262.144 99.936 362.048 0 99.968-99.904 99.968-262.176 0-362.080zM507.904 300.192c27.008 0 48.992 21.984 48.992 49.088 0 27.296-21.984 49.472-48.992 49.472-27.264 0-49.536-22.176-49.536-49.472 0-27.552 21.728-49.088 49.536-49.088zM586.656 660.8c0 10.304-4.96 15.328-15.264 15.328h-126.464c-10.304 0-15.328-5.024-15.328-15.328v-32.256c0-10.304 5.024-15.264 15.328-15.264h23.36v-136.064h-23.872c-10.304 0-15.264-5.024-15.264-15.328v-32.224c0-10.304 4.96-15.264 15.264-15.264h88.288c10.304 0 15.264 4.96 15.264 15.264v183.648h23.424c10.304 0 15.264 4.96 15.264 15.264v32.224z",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`device utils get icon 2`] = `
|
||||
Array [
|
||||
"M693.12 330.88c-46.317-46.267-110.276-74.88-180.919-74.88-141.385 0-256 114.615-256 256s114.615 256 256 256c70.642 0 134.602-28.613 180.921-74.882l-0.002 0.002c46.387-46.337 75.081-110.377 75.081-181.12s-28.694-134.783-75.079-181.118l-0.002-0.002zM494.080 344.32h53.12c16 0 18.24 9.28 18.24 14.72v10.24l-10.88 194.56c0 14.4-8 17.28-18.88 17.28h-28.16c-10.56 0-17.28-2.88-18.88-17.92l-10.88-193.92v-10.56c-1.28-4.8 2.24-14.080 16.32-14.080zM521.28 717.76c-0.095 0.001-0.207 0.001-0.319 0.001-27.747 0-50.24-22.493-50.24-50.24s22.493-50.24 50.24-50.24c27.747 0 50.24 22.493 50.24 50.24 0 0.112 0 0.224-0.001 0.336v-0.017c0 0 0 0.001 0 0.001 0 27.634-22.311 50.057-49.903 50.239h-0.017z",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`device utils get icon 3`] = `
|
||||
Array [
|
||||
"M795.616 735.008l-264.896-465.44c-10.272-18.080-27.168-18.080-37.504 0l-264.864 465.44c-10.272 18.176-1.696 32.992 19.040 32.992h529.184c20.8 0 29.376-14.816 19.040-32.992zM549.76 673.12c0 10.464-8.48 18.976-18.912 18.976h-37.792c-10.336 0-18.912-8.512-18.912-18.976v-37.952c0-10.464 8.576-18.976 18.912-18.976h37.792c10.4 0 18.912 8.544 18.912 18.976v37.952zM549.76 559.264c0 10.464-8.48 18.976-18.912 18.976h-37.792c-10.336 0-18.912-8.512-18.912-18.976v-113.856c0-10.464 8.576-18.976 18.912-18.976h37.792c10.4 0 18.912 8.544 18.912 18.976v113.856z",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`device utils get icon 4`] = `
|
||||
Array [
|
||||
"M692.8 313.92l-1.92-1.92c-6.246-7.057-15.326-11.484-25.44-11.484s-19.194 4.427-25.409 11.448l-0.031 0.036-196.48 224-3.84 1.6-3.84-1.92-48.64-57.28c-7.010-7.905-17.193-12.862-28.533-12.862-21.031 0-38.080 17.049-38.080 38.080 0 7.495 2.165 14.485 5.905 20.377l-0.092-0.155 100.8 148.16c5.391 8.036 14.386 13.292 24.618 13.44h8.662c17.251-0.146 32.385-9.075 41.163-22.529l0.117-0.191 195.2-296.32c4.473-6.632 7.141-14.803 7.141-23.597 0-11.162-4.297-21.32-11.326-28.911l0.025 0.028z",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`device utils get icon 5`] = `undefined`;
|
||||
|
||||
exports[`device utils get icon 6`] = `undefined`;
|
||||
|
||||
exports[`device utils get status 1`] = `"#1E7FF0"`;
|
||||
|
||||
exports[`device utils get status 2`] = `"#ED1212"`;
|
||||
|
||||
exports[`device utils get status 3`] = `"#EB8A00"`;
|
||||
|
||||
exports[`device utils get status 4`] = `"#01B757"`;
|
||||
|
||||
exports[`device utils get status 5`] = `null`;
|
||||
|
||||
exports[`device utils get status 6`] = `null`;
|
32
src/utils/__tests__/notification.test.js
Normal file
32
src/utils/__tests__/notification.test.js
Normal file
@ -0,0 +1,32 @@
|
||||
import * as nUtils from 'utils/notification';
|
||||
|
||||
describe('device utils', () => {
|
||||
it('get status', () => {
|
||||
const types = [
|
||||
'info',
|
||||
'error',
|
||||
'warning',
|
||||
'success',
|
||||
'kdsjflds',
|
||||
'',
|
||||
];
|
||||
|
||||
types.forEach((type) => {
|
||||
expect(nUtils.getColor(type)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it('get icon', () => {
|
||||
const types = [
|
||||
'info',
|
||||
'error',
|
||||
'warning',
|
||||
'success',
|
||||
'kdsjflds',
|
||||
'',
|
||||
];
|
||||
|
||||
types.forEach((type) => {
|
||||
expect(nUtils.getIcon(type)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
80
src/views/Landing/components/BetaDisclaimer/index.js
Normal file
80
src/views/Landing/components/BetaDisclaimer/index.js
Normal file
@ -0,0 +1,80 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import colors from 'config/colors';
|
||||
import icons from 'config/icons';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import Button from 'components/Button';
|
||||
import P from 'components/Paragraph';
|
||||
import { H2 } from 'components/Heading';
|
||||
import * as WalletActions from 'actions/WalletActions';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
const ModalWindow = styled.div`
|
||||
margin: auto;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
background-color: ${colors.WHITE};
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 620px;
|
||||
padding: 24px 48px;
|
||||
`;
|
||||
|
||||
const StyledP = styled(P)`
|
||||
padding: 10px 0px;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
margin: 10px 0px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled(Icon)`
|
||||
position: relative;
|
||||
top: -1px;
|
||||
`;
|
||||
|
||||
const BetaDisclaimer = (props: { close: () => void }) => (
|
||||
<Wrapper>
|
||||
<ModalWindow>
|
||||
<H2>You are opening Trezor Beta Wallet</H2>
|
||||
<StyledP><i>Trezor Beta Wallet</i> is a public feature-testing version of the <i>Trezor Wallet</i>, offering the newest features before they are available to the general public.</StyledP>
|
||||
<StyledP>In contrast, <i>Trezor Wallet</i> is feature-conservative, making sure that its functionality is maximally reliable and dependable for the general public.</StyledP>
|
||||
<StyledP>
|
||||
<StyledIcon
|
||||
size={24}
|
||||
color={colors.WARNING_PRIMARY}
|
||||
icon={icons.WARNING}
|
||||
/>
|
||||
Please note that the <i>Trezor Beta Wallet</i> might be collecting anonymized usage data, especially error logs, for development purposes. The <i>Trezor Wallet</i> does not log any data.
|
||||
</StyledP>
|
||||
<StyledButton onClick={props.close}>OK, I understand</StyledButton>
|
||||
</ModalWindow>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
(dispatch: Dispatch) => ({
|
||||
close: bindActionCreators(WalletActions.hideBetaDisclaimer, dispatch),
|
||||
}),
|
||||
)(BetaDisclaimer);
|
@ -4,6 +4,7 @@ import React from 'react';
|
||||
import { isWebUSB } from 'utils/device';
|
||||
|
||||
import LandingWrapper from 'views/Landing/components/LandingWrapper';
|
||||
import BetaDisclaimer from 'views/Landing/components/BetaDisclaimer';
|
||||
import BrowserNotSupported from 'views/Landing/components/BrowserNotSupported';
|
||||
import ConnectDevice from 'views/Landing/components/ConnectDevice';
|
||||
import InstallBridge from 'views/Landing/views/InstallBridge/Container';
|
||||
@ -16,6 +17,8 @@ const Root = (props: Props) => {
|
||||
const localStorageError = props.localStorage.error;
|
||||
const connectError = props.connect.error;
|
||||
|
||||
if (props.wallet.showBetaDisclaimer) return <BetaDisclaimer />;
|
||||
|
||||
const error = !initialized ? (localStorageError || connectError) : null;
|
||||
const shouldShowUnsupportedBrowser = browserState.supported === false;
|
||||
const shouldShowInstallBridge = initialized && connectError;
|
||||
|
@ -1,18 +1,20 @@
|
||||
/* @flow */
|
||||
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import colors from 'config/colors';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
type Props = {
|
||||
|
||||
pathname: string;
|
||||
wrapper: ?HTMLElement;
|
||||
}
|
||||
|
||||
type State = {
|
||||
style: {
|
||||
width: number;
|
||||
left: number;
|
||||
}
|
||||
},
|
||||
shouldAnimate: boolean,
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@ -22,7 +24,9 @@ const Wrapper = styled.div`
|
||||
width: 100px;
|
||||
height: 2px;
|
||||
background: ${colors.GREEN_PRIMARY};
|
||||
transition: all 0.3s ease-in-out;
|
||||
${props => props.animation && css`
|
||||
transition: all 0.3s ease-in-out;
|
||||
`}
|
||||
`;
|
||||
|
||||
class Indicator extends PureComponent<Props, State> {
|
||||
@ -34,52 +38,62 @@ class Indicator extends PureComponent<Props, State> {
|
||||
width: 0,
|
||||
left: 0,
|
||||
},
|
||||
shouldAnimate: false,
|
||||
};
|
||||
|
||||
this.reposition = this.reposition.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.reposition();
|
||||
window.addEventListener('resize', this.reposition, false);
|
||||
window.addEventListener('resize', this.handleResize, false);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.reposition();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.reposition, false);
|
||||
}
|
||||
|
||||
reposition() {
|
||||
const tabs = document.querySelector('.account-tabs');
|
||||
if (!tabs) return;
|
||||
const active = tabs.querySelector('.active');
|
||||
if (!active) return;
|
||||
const bounds = active.getBoundingClientRect();
|
||||
|
||||
const left = bounds.left - tabs.getBoundingClientRect().left;
|
||||
|
||||
if (this.state.style.left !== left) {
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
if (this.props.pathname !== newProps.pathname) {
|
||||
this.setState({
|
||||
style: {
|
||||
width: bounds.width,
|
||||
left,
|
||||
},
|
||||
shouldAnimate: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
reposition: () => void;
|
||||
componentDidUpdate() {
|
||||
this.reposition(false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize, false);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.reposition();
|
||||
}
|
||||
|
||||
handleResize: () => void;
|
||||
|
||||
reposition(resetAnimation: boolean = true) {
|
||||
if (!this.props.wrapper) return;
|
||||
const { wrapper } = this.props;
|
||||
const active = wrapper.querySelector('.active');
|
||||
if (!active) return;
|
||||
const bounds = active.getBoundingClientRect();
|
||||
const left = bounds.left - wrapper.getBoundingClientRect().left;
|
||||
const { state } = this;
|
||||
|
||||
if (state.style.left !== left) {
|
||||
this.setState({
|
||||
style: {
|
||||
width: bounds.width,
|
||||
left,
|
||||
},
|
||||
shouldAnimate: resetAnimation ? false : state.shouldAnimate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.wrapper) return null;
|
||||
return (
|
||||
<Wrapper style={this.state.style} />
|
||||
<Wrapper style={this.state.style} animation={this.state.shouldAnimate} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -47,22 +47,28 @@ const StyledNavLink = styled(NavLink)`
|
||||
}
|
||||
`;
|
||||
|
||||
class TopNavigationAccount extends React.PureComponent<Props> {
|
||||
wrapperRefCallback = (element: ?HTMLElement) => {
|
||||
this.wrapper = element;
|
||||
}
|
||||
|
||||
const TopNavigationAccount = (props: Props) => {
|
||||
const { state, pathname } = props.location;
|
||||
if (!state) return null;
|
||||
wrapper: ?HTMLElement;
|
||||
|
||||
const basePath = `/device/${state.device}/network/${state.network}/account/${state.account}`;
|
||||
render() {
|
||||
const { state, pathname } = this.props.location;
|
||||
if (!state) return null;
|
||||
|
||||
return (
|
||||
<Wrapper className="account-tabs">
|
||||
<StyledNavLink exact to={`${basePath}`}>Summary</StyledNavLink>
|
||||
<StyledNavLink to={`${basePath}/receive`}>Receive</StyledNavLink>
|
||||
<StyledNavLink to={`${basePath}/send`}>Send</StyledNavLink>
|
||||
{/* <StyledNavLink to={`${basePath}/signverify`}>Sign & Verify</StyledNavLink> */}
|
||||
<Indicator pathname={pathname} />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
const basePath = `/device/${state.device}/network/${state.network}/account/${state.account}`;
|
||||
return (
|
||||
<Wrapper className="account-tabs" innerRef={this.wrapperRefCallback}>
|
||||
<StyledNavLink exact to={`${basePath}`}>Summary</StyledNavLink>
|
||||
<StyledNavLink to={`${basePath}/receive`}>Receive</StyledNavLink>
|
||||
<StyledNavLink to={`${basePath}/send`}>Send</StyledNavLink>
|
||||
{/* <StyledNavLink to={`${basePath}/signverify`}>Sign & Verify</StyledNavLink> */}
|
||||
<Indicator pathname={pathname} wrapper={this.wrapper} />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TopNavigationAccount;
|
@ -243,6 +243,7 @@ const AccountSend = (props: Props) => {
|
||||
}
|
||||
|
||||
const tokensSelectData = getTokensSelectData(tokens, network);
|
||||
const tokensSelectValue = tokensSelectData.find(t => t.value === currency);
|
||||
const isAdvancedSettingsHidden = !advanced;
|
||||
|
||||
return (
|
||||
@ -310,7 +311,7 @@ const AccountSend = (props: Props) => {
|
||||
key="currency"
|
||||
isSearchable={false}
|
||||
isClearable={false}
|
||||
defaultValue={tokensSelectData[0]}
|
||||
value={tokensSelectValue}
|
||||
isDisabled={tokensSelectData.length < 2}
|
||||
onChange={onCurrencyChange}
|
||||
options={tokensSelectData}
|
||||
|
Loading…
Reference in New Issue
Block a user