1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-30 20:28:09 +00:00

local storage prefix

- common prefix for localstorage and sessionstorage
- code refactoring
This commit is contained in:
Szymon Lesisz 2018-10-22 10:04:19 +02:00
parent a534830dff
commit 0c61bcb08d
3 changed files with 123 additions and 86 deletions

View File

@ -10,6 +10,7 @@ import * as PENDING from 'actions/constants/pendingTx';
import * as WALLET from 'actions/constants/wallet'; import * as WALLET from 'actions/constants/wallet';
import { httpRequest } from 'utils/networkUtils'; import { httpRequest } from 'utils/networkUtils';
import * as buildUtils from 'utils/build'; import * as buildUtils from 'utils/build';
import * as storageUtils from 'utils/storage';
import { findAccountTokens } from 'reducers/TokensReducer'; import { findAccountTokens } from 'reducers/TokensReducer';
import type { Account } from 'reducers/AccountsReducer'; import type { Account } from 'reducers/AccountsReducer';
@ -17,7 +18,6 @@ import type { Token } from 'reducers/TokensReducer';
import type { PendingTx } from 'reducers/PendingTxReducer'; import type { PendingTx } from 'reducers/PendingTxReducer';
import type { Discovery } from 'reducers/DiscoveryReducer'; import type { Discovery } from 'reducers/DiscoveryReducer';
import type { import type {
TrezorDevice, TrezorDevice,
ThunkAction, ThunkAction,
@ -43,22 +43,15 @@ export type StorageAction = {
error: string, error: string,
}; };
const get = (key: string): ?string => { const TYPE: 'local' = 'local';
try { const { STORAGE_PATH } = storageUtils;
return window.localStorage.getItem(key); const KEY_VERSION: string = `${STORAGE_PATH}version`;
} catch (error) { const KEY_DEVICES: string = `${STORAGE_PATH}devices`;
// available = false; const KEY_ACCOUNTS: string = `${STORAGE_PATH}accounts`;
return null; const KEY_DISCOVERY: string = `${STORAGE_PATH}discovery`;
} const KEY_TOKENS: string = `${STORAGE_PATH}tokens`;
}; const KEY_PENDING: string = `${STORAGE_PATH}pending`;
const KEY_BETA_MODAL: string = '/betaModalPrivacy'; // this key needs to be compatible with "parent" (old) wallet
const set = (key: string, value: string | boolean): void => {
try {
window.localStorage.setItem(key, value);
} catch (error) {
console.error(`Local Storage ERROR: ${error}`);
}
};
// https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js // https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js
// or // or
@ -80,25 +73,25 @@ export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState):
const discovery: Array<Discovery> = findDiscovery(devices, getState().discovery); const discovery: Array<Discovery> = findDiscovery(devices, getState().discovery);
// save devices // save devices
set('devices', JSON.stringify(devices)); storageUtils.set(TYPE, KEY_DEVICES, JSON.stringify(devices));
// save already preloaded accounts // save already preloaded accounts
set('accounts', JSON.stringify(accounts)); storageUtils.set(TYPE, KEY_ACCOUNTS, JSON.stringify(accounts));
// save discovery state // save discovery state
set('discovery', JSON.stringify(discovery)); storageUtils.set(TYPE, KEY_DISCOVERY, JSON.stringify(discovery));
// tokens // tokens
set('tokens', JSON.stringify(tokens)); storageUtils.set(TYPE, KEY_TOKENS, JSON.stringify(tokens));
// pending transactions // pending transactions
set('pending', JSON.stringify(pending)); storageUtils.set(TYPE, KEY_PENDING, JSON.stringify(pending));
}; };
export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch): void => { export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch): void => {
if (!event.newValue) return; if (!event.newValue) return;
if (event.key === 'devices') { if (event.key === KEY_DEVICES) {
// check if device was added/ removed // check if device was added/ removed
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue); // const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features); // const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
@ -113,28 +106,28 @@ export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch)
// const diff = oldDevices.filter(d => newDevices.indexOf()) // const diff = oldDevices.filter(d => newDevices.indexOf())
} }
if (event.key === 'accounts') { if (event.key === KEY_ACCOUNTS) {
dispatch({ dispatch({
type: ACCOUNT.FROM_STORAGE, type: ACCOUNT.FROM_STORAGE,
payload: JSON.parse(event.newValue), payload: JSON.parse(event.newValue),
}); });
} }
if (event.key === 'tokens') { if (event.key === KEY_TOKENS) {
dispatch({ dispatch({
type: TOKEN.FROM_STORAGE, type: TOKEN.FROM_STORAGE,
payload: JSON.parse(event.newValue), payload: JSON.parse(event.newValue),
}); });
} }
if (event.key === 'pending') { if (event.key === KEY_PENDING) {
dispatch({ dispatch({
type: PENDING.FROM_STORAGE, type: PENDING.FROM_STORAGE,
payload: JSON.parse(event.newValue), payload: JSON.parse(event.newValue),
}); });
} }
if (event.key === 'discovery') { if (event.key === KEY_DISCOVERY) {
dispatch({ dispatch({
type: DISCOVERY.FROM_STORAGE, type: DISCOVERY.FROM_STORAGE,
payload: JSON.parse(event.newValue), payload: JSON.parse(event.newValue),
@ -142,14 +135,13 @@ export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch)
} }
}; };
const VERSION: string = '1';
const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> => { const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
if (typeof window.localStorage === 'undefined') return; if (typeof window.localStorage === 'undefined') return;
try { try {
const config: Config = await httpRequest(AppConfigJSON, 'json'); const config: Config = await httpRequest(AppConfigJSON, 'json');
// remove ropsten testnet from config networks
if (!buildUtils.isDev()) { if (!buildUtils.isDev()) {
const index = config.networks.findIndex(c => c.shortcut === 'trop'); const index = config.networks.findIndex(c => c.shortcut === 'trop');
delete config.networks[index]; delete config.networks[index];
@ -161,18 +153,6 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> =>
dispatch(update(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 // load tokens
const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => { const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => {
const collection: TokensCollection = await promise; const collection: TokensCollection = await promise;
@ -195,9 +175,17 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> =>
} }
}; };
const VERSION: string = '1';
const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
const devices: ?string = get('devices'); // validate version
const version: ?string = storageUtils.get(TYPE, KEY_VERSION);
if (version && version !== VERSION) {
storageUtils.clearAll();
}
storageUtils.set(TYPE, KEY_VERSION, VERSION);
const devices: ?string = storageUtils.get(TYPE, KEY_DEVICES);
if (devices) { if (devices) {
dispatch({ dispatch({
type: CONNECT.DEVICE_FROM_STORAGE, type: CONNECT.DEVICE_FROM_STORAGE,
@ -205,7 +193,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
}); });
} }
const accounts: ?string = get('accounts'); const accounts: ?string = storageUtils.get(TYPE, KEY_ACCOUNTS);
if (accounts) { if (accounts) {
dispatch({ dispatch({
type: ACCOUNT.FROM_STORAGE, type: ACCOUNT.FROM_STORAGE,
@ -213,7 +201,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
}); });
} }
const userTokens: ?string = get('tokens'); const userTokens: ?string = storageUtils.get(TYPE, KEY_TOKENS);
if (userTokens) { if (userTokens) {
dispatch({ dispatch({
type: TOKEN.FROM_STORAGE, type: TOKEN.FROM_STORAGE,
@ -221,7 +209,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
}); });
} }
const pending: ?string = get('pending'); const pending: ?string = storageUtils.get(TYPE, KEY_PENDING);
if (pending) { if (pending) {
dispatch({ dispatch({
type: PENDING.FROM_STORAGE, type: PENDING.FROM_STORAGE,
@ -229,7 +217,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
}); });
} }
const discovery: ?string = get('discovery'); const discovery: ?string = storageUtils.get(TYPE, KEY_DISCOVERY);
if (discovery) { if (discovery) {
dispatch({ dispatch({
type: DISCOVERY.FROM_STORAGE, type: DISCOVERY.FROM_STORAGE,
@ -238,7 +226,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
} }
if (buildUtils.isDev() || buildUtils.isBeta()) { if (buildUtils.isDev() || buildUtils.isBeta()) {
const betaModal = get('/betaModalPrivacy'); const betaModal = Object.keys(window.localStorage).find(key => key.indexOf(KEY_BETA_MODAL) >= 0);
if (!betaModal) { if (!betaModal) {
dispatch({ dispatch({
type: WALLET.SHOW_BETA_DISCLAIMER, type: WALLET.SHOW_BETA_DISCLAIMER,
@ -258,6 +246,6 @@ export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
}; };
export const hideBetaDisclaimer = (): ThunkAction => (dispatch: Dispatch): void => { export const hideBetaDisclaimer = (): ThunkAction => (dispatch: Dispatch): void => {
set('/betaModalPrivacy', true); storageUtils.set(TYPE, KEY_BETA_MODAL, true);
dispatch(loadJSON()); dispatch(loadJSON());
}; };

View File

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
import * as storageUtils from 'utils/storage';
import type { State as SendFormState } from 'reducers/SendFormReducer'; import type { State as SendFormState } from 'reducers/SendFormReducer';
import type { import type {
@ -9,52 +9,39 @@ import type {
Dispatch, Dispatch,
} from 'flowtype'; } from 'flowtype';
const TX_PREFIX: string = 'trezor:draft-tx:'; const TYPE: 'session' = 'session';
const { STORAGE_PATH } = storageUtils;
const KEY_TX_DRAFT: string = `${STORAGE_PATH}txdraft`;
const getTxDraftKey = (getState: GetState): string => {
const { pathname } = getState().router.location;
return `${KEY_TX_DRAFT}${pathname}`;
};
export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
if (typeof window.localStorage === 'undefined') return;
const state = getState().sendForm; const state = getState().sendForm;
if (state.untouched) return; if (state.untouched) return;
const location = getState().router.location.pathname; const key = getTxDraftKey(getState);
try { storageUtils.set(TYPE, key, JSON.stringify(state));
// save state as it is
// "loadDraftTransaction" will do the validation
window.sessionStorage.setItem(`${TX_PREFIX}${location}`, JSON.stringify(state));
} catch (error) {
console.error(`Saving sessionStorage error: ${error}`);
}
}; };
export const loadDraftTransaction = (): PayloadAction<?SendFormState> => (dispatch: Dispatch, getState: GetState): ?SendFormState => { export const loadDraftTransaction = (): PayloadAction<?SendFormState> => (dispatch: Dispatch, getState: GetState): ?SendFormState => {
if (typeof window.localStorage === 'undefined') return null; const key = getTxDraftKey(getState);
const value: ?string = storageUtils.get(TYPE, key);
try { if (!value) return null;
const location = getState().router.location.pathname; const state: ?SendFormState = JSON.parse(value);
const value: string = window.sessionStorage.getItem(`${TX_PREFIX}${location}`); if (!state) return null;
const state: ?SendFormState = JSON.parse(value); // decide if draft is valid and should be returned
if (state) { // ignore this draft if has any error
// decide if draft is valid and should be returned if (Object.keys(state.errors).length > 0) {
// ignore this draft if has any error storageUtils.remove(TYPE, key);
if (Object.keys(state.errors).length > 0) { return null;
window.sessionStorage.removeItem(`${TX_PREFIX}${location}`);
return null;
}
return state;
}
} catch (error) {
console.error(`Loading sessionStorage error: ${error}`);
} }
return null; return state;
}; };
export const clear = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { export const clear = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
if (typeof window.localStorage === 'undefined') return; const key = getTxDraftKey(getState);
const location = getState().router.location.pathname; storageUtils.remove(TYPE, key);
try {
window.sessionStorage.removeItem(`${TX_PREFIX}${location}`);
} catch (error) {
console.error(`Clearing sessionStorage error: ${error}`);
}
}; };

62
src/utils/storage.js Normal file
View File

@ -0,0 +1,62 @@
/* @flow */
// Copy-paste from mytrezor (old wallet)
// https://github.com/satoshilabs/mytrezor/blob/develop/app/scripts/storage/BackendLocalStorage.js
export const getStoragePath = (): string => {
const regexHash = /^([^#]*)#.*$/;
const path = window.location.href.replace(regexHash, '$1');
const regexStart = /^[^:]*:\/\/[^/]*\//;
return path.replace(regexStart, '/');
};
export const STORAGE_PATH: string = getStoragePath();
type StorageType = 'local' | 'session';
export const get: <T>(type: StorageType, key: string) => T | null = (type, key) => {
const storage = type === 'local' ? window.localStorage : window.sessionStorage;
try {
return storage.getItem(key);
} catch (error) {
console.warn(`Get ${type} storage: ${error}`);
return null;
}
};
export const set = (type: StorageType, key: string, value: string | number | boolean): void => {
const storage = type === 'local' ? window.localStorage : window.sessionStorage;
try {
storage.setItem(key, value);
} catch (error) {
console.warn(`Save ${type} storage: ${error}`);
}
};
export const remove = (type: StorageType, key: string): void => {
const storage = type === 'local' ? window.localStorage : window.sessionStorage;
try {
storage.removeItem(key);
} catch (error) {
console.warn(`Remove ${type} storage: ${error}`);
}
};
export const clearAll = (type: ?StorageType): void => {
let clearLocal: boolean = true;
let clearSession: boolean = true;
if (typeof type === 'string') {
clearLocal = type === 'local';
clearSession = !clearLocal;
}
try {
if (clearLocal) {
Object.keys(window.localStorage).forEach(key => key.indexOf(STORAGE_PATH) >= 0 && window.localStorage.removeItem(key));
}
if (clearSession) {
Object.keys(window.sessionStorage).forEach(key => key.indexOf(STORAGE_PATH) >= 0 && window.sessionStorage.removeItem(key));
}
} catch (error) {
console.error(`Clearing sessionStorage error: ${error}`);
}
};