Use async storage instead of localstorage (#9919)

feature/default_network_editable
David Walsh 4 years ago committed by GitHub
parent 9f6fa64d67
commit bf65c979d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      package.json
  2. 44
      ui/app/ducks/gas/gas-duck.test.js
  3. 232
      ui/app/ducks/gas/gas.duck.js
  4. 22
      ui/app/ducks/swaps/swaps.js
  5. 9
      ui/app/helpers/utils/fetch-with-cache.js
  6. 12
      ui/app/helpers/utils/fetch-with-cache.test.js
  7. 2
      ui/app/hooks/useRetryTransaction.js
  8. 20
      ui/lib/local-storage-helpers.js
  9. 23
      ui/lib/storage-helpers.js
  10. 7
      yarn.lock

@ -131,6 +131,7 @@
"json-rpc-engine": "^5.3.0",
"json-rpc-middleware-stream": "^2.1.1",
"jsonschema": "^1.2.4",
"localforage": "^1.9.0",
"lodash": "^4.17.19",
"loglevel": "^1.4.1",
"luxon": "^1.24.1",

@ -2,10 +2,10 @@ import assert from 'assert'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
const fakeLocalStorage = {}
const fakeStorage = {}
const GasDuck = proxyquire('./gas.duck.js', {
'../../../lib/local-storage-helpers': fakeLocalStorage,
'../../../lib/storage-helpers': fakeStorage,
})
const {
@ -160,8 +160,8 @@ describe('Gas Duck', function () {
tempFetch = window.fetch
tempDateNow = global.Date.now
fakeLocalStorage.loadLocalStorageData = sinon.stub()
fakeLocalStorage.saveLocalStorageData = sinon.spy()
fakeStorage.getStorageItem = sinon.stub()
fakeStorage.setStorageItem = sinon.spy()
window.fetch = sinon.stub().callsFake(fakeFetch)
global.Date.now = () => 2000000
})
@ -412,21 +412,19 @@ describe('Gas Duck', function () {
])
})
it('should fetch recently retrieved estimates from local storage', async function () {
it('should fetch recently retrieved estimates from storage', async function () {
const mockDistpatch = sinon.spy()
fakeLocalStorage.loadLocalStorageData
fakeStorage.getStorageItem
.withArgs('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED')
.returns(2000000 - 1) // one second ago from "now"
fakeLocalStorage.loadLocalStorageData
.withArgs('BASIC_PRICE_ESTIMATES')
.returns({
average: 25,
blockTime: 'mockBlock_time',
blockNum: 'mockBlockNum',
fast: 35,
fastest: 45,
safeLow: 15,
})
fakeStorage.getStorageItem.withArgs('BASIC_PRICE_ESTIMATES').returns({
average: 25,
blockTime: 'mockBlock_time',
blockNum: 'mockBlockNum',
fast: 35,
fastest: 45,
safeLow: 15,
})
await fetchBasicGasEstimates()(mockDistpatch, () => ({
gas: { ...initState },
@ -453,9 +451,9 @@ describe('Gas Duck', function () {
])
})
it('should fallback to network if retrieving estimates from local storage fails', async function () {
it('should fallback to network if retrieving estimates from storage fails', async function () {
const mockDistpatch = sinon.spy()
fakeLocalStorage.loadLocalStorageData
fakeStorage.getStorageItem
.withArgs('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED')
.returns(2000000 - 1) // one second ago from "now"
@ -541,12 +539,12 @@ describe('Gas Duck', function () {
])
})
it('should fetch recently retrieved estimates from local storage', async function () {
it('should fetch recently retrieved estimates from storage', async function () {
const mockDistpatch = sinon.spy()
fakeLocalStorage.loadLocalStorageData
fakeStorage.getStorageItem
.withArgs('BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED')
.returns(2000000 - 1) // one second ago from "now"
fakeLocalStorage.loadLocalStorageData
fakeStorage.getStorageItem
.withArgs('BASIC_GAS_AND_TIME_API_ESTIMATES')
.returns({
average: 5,
@ -596,9 +594,9 @@ describe('Gas Duck', function () {
])
})
it('should fallback to network if retrieving estimates from local storage fails', async function () {
it('should fallback to network if retrieving estimates from storage fails', async function () {
const mockDistpatch = sinon.spy()
fakeLocalStorage.loadLocalStorageData
fakeStorage.getStorageItem
.withArgs('BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED')
.returns(2000000 - 1) // one second ago from "now"

@ -1,9 +1,7 @@
import { uniqBy, cloneDeep, flatten } from 'lodash'
import BigNumber from 'bignumber.js'
import {
loadLocalStorageData,
saveLocalStorageData,
} from '../../../lib/local-storage-helpers'
import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers'
import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util'
import { isEthereumNetwork } from '../../selectors'
@ -209,7 +207,7 @@ export function fetchBasicGasEstimates() {
const { basicPriceEstimatesLastRetrieved } = getState().gas
const timeLastRetrieved =
basicPriceEstimatesLastRetrieved ||
loadLocalStorageData('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED') ||
(await getStorageItem('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED')) ||
0
dispatch(basicGasEstimatesLoadingStarted())
@ -218,7 +216,7 @@ export function fetchBasicGasEstimates() {
if (Date.now() - timeLastRetrieved > 75000) {
basicEstimates = await fetchExternalBasicGasEstimates(dispatch)
} else {
const cachedBasicEstimates = loadLocalStorageData('BASIC_PRICE_ESTIMATES')
const cachedBasicEstimates = await getStorageItem('BASIC_PRICE_ESTIMATES')
basicEstimates =
cachedBasicEstimates || (await fetchExternalBasicGasEstimates(dispatch))
}
@ -259,8 +257,10 @@ async function fetchExternalBasicGasEstimates(dispatch) {
}
const timeRetrieved = Date.now()
saveLocalStorageData(basicEstimates, 'BASIC_PRICE_ESTIMATES')
saveLocalStorageData(timeRetrieved, 'BASIC_PRICE_ESTIMATES_LAST_RETRIEVED')
await Promise.all([
setStorageItem('BASIC_PRICE_ESTIMATES', basicEstimates),
setStorageItem('BASIC_PRICE_ESTIMATES_LAST_RETRIEVED', timeRetrieved),
])
dispatch(setBasicPriceEstimatesLastRetrieved(timeRetrieved))
return basicEstimates
@ -271,7 +271,9 @@ export function fetchBasicGasAndTimeEstimates() {
const { basicPriceAndTimeEstimatesLastRetrieved } = getState().gas
const timeLastRetrieved =
basicPriceAndTimeEstimatesLastRetrieved ||
loadLocalStorageData('BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED') ||
(await getStorageItem(
'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED',
)) ||
0
dispatch(basicGasEstimatesLoadingStarted())
@ -280,7 +282,7 @@ export function fetchBasicGasAndTimeEstimates() {
if (Date.now() - timeLastRetrieved > 75000) {
basicEstimates = await fetchExternalBasicGasAndTimeEstimates(dispatch)
} else {
const cachedBasicEstimates = loadLocalStorageData(
const cachedBasicEstimates = await getStorageItem(
'BASIC_GAS_AND_TIME_API_ESTIMATES',
)
basicEstimates =
@ -332,11 +334,13 @@ async function fetchExternalBasicGasAndTimeEstimates(dispatch) {
}
const timeRetrieved = Date.now()
saveLocalStorageData(basicEstimates, 'BASIC_GAS_AND_TIME_API_ESTIMATES')
saveLocalStorageData(
timeRetrieved,
'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED',
)
await Promise.all([
setStorageItem('BASIC_GAS_AND_TIME_API_ESTIMATES', basicEstimates),
setStorageItem(
'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED',
timeRetrieved,
),
])
dispatch(setBasicApiEstimatesLastRetrieved(timeRetrieved))
return basicEstimates
@ -403,11 +407,11 @@ function inliersByIQR(data, prop) {
}
export function fetchGasEstimates(blockTime) {
return (dispatch, getState) => {
return async (dispatch, getState) => {
const state = getState()
if (!isEthereumNetwork(state)) {
return Promise.resolve(null)
return
}
const {
@ -416,114 +420,112 @@ export function fetchGasEstimates(blockTime) {
} = state.gas
const timeLastRetrieved =
priceAndTimeEstimatesLastRetrieved ||
loadLocalStorageData('GAS_API_ESTIMATES_LAST_RETRIEVED') ||
(await getStorageItem('GAS_API_ESTIMATES_LAST_RETRIEVED')) ||
0
dispatch(gasEstimatesLoadingStarted())
const promiseToFetch =
Date.now() - timeLastRetrieved > 75000
? queryEthGasStationPredictionTable()
.then((r) => r.json())
.then((r) => {
const estimatedPricesAndTimes = r.map(
({ expectedTime, expectedWait, gasprice }) => ({
expectedTime,
expectedWait,
gasprice,
}),
)
const estimatedTimeWithUniquePrices = uniqBy(
estimatedPricesAndTimes,
({ expectedTime }) => expectedTime,
)
const withSupplementalTimeEstimates = flatten(
estimatedTimeWithUniquePrices.map(
({ expectedWait, gasprice }, i, arr) => {
const next = arr[i + 1]
if (!next) {
return [{ expectedWait, gasprice }]
}
const supplementalPrice = getRandomArbitrary(
gasprice,
next.gasprice,
)
const supplementalTime = extrapolateY({
higherY: next.expectedWait,
lowerY: expectedWait,
higherX: next.gasprice,
lowerX: gasprice,
xForExtrapolation: supplementalPrice,
})
const supplementalPrice2 = getRandomArbitrary(
supplementalPrice,
next.gasprice,
)
const supplementalTime2 = extrapolateY({
higherY: next.expectedWait,
lowerY: supplementalTime,
higherX: next.gasprice,
lowerX: supplementalPrice,
xForExtrapolation: supplementalPrice2,
})
return [
{ expectedWait, gasprice },
{
expectedWait: supplementalTime,
gasprice: supplementalPrice,
},
{
expectedWait: supplementalTime2,
gasprice: supplementalPrice2,
},
]
},
),
)
const withOutliersRemoved = inliersByIQR(
withSupplementalTimeEstimates.slice(0).reverse(),
'expectedWait',
).reverse()
const timeMappedToSeconds = withOutliersRemoved.map(
({ expectedWait, gasprice }) => {
const expectedTime = new BigNumber(expectedWait)
.times(Number(blockTime), 10)
.toNumber()
return {
expectedTime,
gasprice: new BigNumber(gasprice, 10).toNumber(),
}
},
)
const timeRetrieved = Date.now()
dispatch(setApiEstimatesLastRetrieved(timeRetrieved))
saveLocalStorageData(
timeRetrieved,
'GAS_API_ESTIMATES_LAST_RETRIEVED',
)
saveLocalStorageData(timeMappedToSeconds, 'GAS_API_ESTIMATES')
return timeMappedToSeconds
const shouldGetFreshGasQuote = Date.now() - timeLastRetrieved > 75000
let estimates
if (shouldGetFreshGasQuote) {
const response = await queryEthGasStationPredictionTable()
const tableJson = await response.json()
const estimatedPricesAndTimes = tableJson.map(
({ expectedTime, expectedWait, gasprice }) => ({
expectedTime,
expectedWait,
gasprice,
}),
)
const estimatedTimeWithUniquePrices = uniqBy(
estimatedPricesAndTimes,
({ expectedTime }) => expectedTime,
)
const withSupplementalTimeEstimates = flatten(
estimatedTimeWithUniquePrices.map(
({ expectedWait, gasprice }, i, arr) => {
const next = arr[i + 1]
if (!next) {
return [{ expectedWait, gasprice }]
}
const supplementalPrice = getRandomArbitrary(
gasprice,
next.gasprice,
)
const supplementalTime = extrapolateY({
higherY: next.expectedWait,
lowerY: expectedWait,
higherX: next.gasprice,
lowerX: gasprice,
xForExtrapolation: supplementalPrice,
})
: Promise.resolve(
priceAndTimeEstimates.length
? priceAndTimeEstimates
: loadLocalStorageData('GAS_API_ESTIMATES'),
)
return promiseToFetch.then((estimates) => {
dispatch(setPricesAndTimeEstimates(estimates))
dispatch(gasEstimatesLoadingFinished())
})
const supplementalPrice2 = getRandomArbitrary(
supplementalPrice,
next.gasprice,
)
const supplementalTime2 = extrapolateY({
higherY: next.expectedWait,
lowerY: supplementalTime,
higherX: next.gasprice,
lowerX: supplementalPrice,
xForExtrapolation: supplementalPrice2,
})
return [
{ expectedWait, gasprice },
{
expectedWait: supplementalTime,
gasprice: supplementalPrice,
},
{
expectedWait: supplementalTime2,
gasprice: supplementalPrice2,
},
]
},
),
)
const withOutliersRemoved = inliersByIQR(
withSupplementalTimeEstimates.slice(0).reverse(),
'expectedWait',
).reverse()
const timeMappedToSeconds = withOutliersRemoved.map(
({ expectedWait, gasprice }) => {
const expectedTime = new BigNumber(expectedWait)
.times(Number(blockTime), 10)
.toNumber()
return {
expectedTime,
gasprice: new BigNumber(gasprice, 10).toNumber(),
}
},
)
const timeRetrieved = Date.now()
dispatch(setApiEstimatesLastRetrieved(timeRetrieved))
await Promise.all([
setStorageItem('GAS_API_ESTIMATES_LAST_RETRIEVED', timeRetrieved),
setStorageItem('GAS_API_ESTIMATES', timeMappedToSeconds),
])
estimates = timeMappedToSeconds
} else if (priceAndTimeEstimates.length) {
estimates = priceAndTimeEstimates
} else {
estimates = await getStorageItem('GAS_API_ESTIMATES')
}
dispatch(setPricesAndTimeEstimates(estimates))
dispatch(gasEstimatesLoadingFinished())
}
}
export function setCustomGasPriceForRetry(newPrice) {
return (dispatch) => {
return async (dispatch) => {
if (newPrice === '0x0') {
const { fast } = loadLocalStorageData('BASIC_PRICE_ESTIMATES')
const { fast } = await getStorageItem('BASIC_PRICE_ESTIMATES')
dispatch(setCustomGasPrice(decGWEIToHexWEI(fast)))
} else {
dispatch(setCustomGasPrice(newPrice))

@ -2,10 +2,8 @@ import { createSlice } from '@reduxjs/toolkit'
import BigNumber from 'bignumber.js'
import log from 'loglevel'
import {
loadLocalStorageData,
saveLocalStorageData,
} from '../../../lib/local-storage-helpers'
import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers'
import {
addToken,
addUnapprovedTransaction,
@ -754,7 +752,7 @@ export function fetchMetaSwapsGasPriceEstimates() {
)
const timeLastRetrieved =
priceEstimatesLastRetrieved ||
loadLocalStorageData('METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED') ||
(await getStorageItem('METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED')) ||
0
dispatch(swapGasPriceEstimatesFetchStarted())
@ -764,7 +762,7 @@ export function fetchMetaSwapsGasPriceEstimates() {
if (Date.now() - timeLastRetrieved > 30000) {
priceEstimates = await fetchSwapsGasPrices()
} else {
const cachedPriceEstimates = loadLocalStorageData(
const cachedPriceEstimates = await getStorageItem(
'METASWAP_GAS_PRICE_ESTIMATES',
)
priceEstimates = cachedPriceEstimates || (await fetchSwapsGasPrices())
@ -795,11 +793,13 @@ export function fetchMetaSwapsGasPriceEstimates() {
const timeRetrieved = Date.now()
saveLocalStorageData(priceEstimates, 'METASWAP_GAS_PRICE_ESTIMATES')
saveLocalStorageData(
timeRetrieved,
'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED',
)
await Promise.all([
setStorageItem('METASWAP_GAS_PRICE_ESTIMATES', priceEstimates),
setStorageItem(
'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED',
timeRetrieved,
),
])
dispatch(
swapGasPriceEstimatesFetchCompleted({

@ -1,7 +1,4 @@
import {
loadLocalStorageData,
saveLocalStorageData,
} from '../../../lib/local-storage-helpers'
import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers'
import fetchWithTimeout from '../../../../app/scripts/lib/fetch-with-timeout'
const fetchWithCache = async (
@ -27,7 +24,7 @@ const fetchWithCache = async (
}
const currentTime = Date.now()
const cachedFetch = loadLocalStorageData('cachedFetch') || {}
const cachedFetch = (await getStorageItem('cachedFetch')) || {}
const { cachedResponse, cachedTime } = cachedFetch[url] || {}
if (cachedResponse && currentTime - cachedTime < cacheRefreshTime) {
return cachedResponse
@ -52,7 +49,7 @@ const fetchWithCache = async (
cachedTime: currentTime,
}
cachedFetch[url] = cacheEntry
saveLocalStorageData(cachedFetch, 'cachedFetch')
await setStorageItem('cachedFetch', cachedFetch)
return responseJson
}

@ -3,15 +3,15 @@ import nock from 'nock'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
const fakeLocalStorageHelpers = {}
const fakeStorage = {}
const fetchWithCache = proxyquire('./fetch-with-cache', {
'../../../lib/local-storage-helpers': fakeLocalStorageHelpers,
'../../../lib/storage-helpers': fakeStorage,
}).default
describe('Fetch with cache', function () {
beforeEach(function () {
fakeLocalStorageHelpers.loadLocalStorageData = sinon.stub()
fakeLocalStorageHelpers.saveLocalStorageData = sinon.stub()
fakeStorage.getStorageItem = sinon.stub()
fakeStorage.setStorageItem = sinon.stub()
})
afterEach(function () {
sinon.restore()
@ -36,7 +36,7 @@ describe('Fetch with cache', function () {
.get('/price')
.reply(200, '{"average": 2}')
fakeLocalStorageHelpers.loadLocalStorageData.returns({
fakeStorage.getStorageItem.returns({
'https://fetchwithcache.metamask.io/price': {
cachedResponse: { average: 1 },
cachedTime: Date.now(),
@ -56,7 +56,7 @@ describe('Fetch with cache', function () {
.get('/price')
.reply(200, '{"average": 3}')
fakeLocalStorageHelpers.loadLocalStorageData.returns({
fakeStorage.getStorageItem.returns({
'https://fetchwithcache.metamask.io/cached': {
cachedResponse: { average: 1 },
cachedTime: Date.now() - 1000,

@ -38,7 +38,7 @@ export function useRetryTransaction(transactionGroup) {
await dispatch(fetchGasEstimates(basicEstimates.blockTime))
const transaction = initialTransaction
const increasedGasPrice = increaseLastGasPrice(gasPrice)
dispatch(
await dispatch(
setCustomGasPriceForRetry(
increasedGasPrice || transaction.txParams.gasPrice,
),

@ -1,20 +0,0 @@
export function loadLocalStorageData(itemKey) {
try {
const serializedData = window.localStorage.getItem(itemKey)
if (serializedData === null) {
return undefined
}
return JSON.parse(serializedData)
} catch (err) {
return undefined
}
}
export function saveLocalStorageData(data, itemKey) {
try {
const serializedData = JSON.stringify(data)
window.localStorage.setItem(itemKey, serializedData)
} catch (err) {
console.warn(err)
}
}

@ -0,0 +1,23 @@
import localforage from 'localforage'
export async function getStorageItem(key) {
try {
const serializedData = await localforage.getItem(key)
if (serializedData === null) {
return undefined
}
return JSON.parse(serializedData)
} catch (err) {
return undefined
}
}
export async function setStorageItem(key, value) {
try {
const serializedData = JSON.stringify(value)
await localforage.setItem(key, serializedData)
} catch (err) {
console.warn(err)
}
}

@ -16608,6 +16608,13 @@ localforage@1.8.1:
dependencies:
lie "3.1.1"
localforage@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1"
integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==
dependencies:
lie "3.1.1"
localstorage-down@^0.6.7:
version "0.6.7"
resolved "https://registry.yarnpkg.com/localstorage-down/-/localstorage-down-0.6.7.tgz#d0799a93b31e6c5fa5188ec06242eb1cce9d6d15"

Loading…
Cancel
Save