1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Fetch with a timeout everywhere (#10101)

* Use fetchWithTimeout everywhere
* Memoize getFetchWithTimeout
* Require specified timeout
This commit is contained in:
Erik Marks 2021-01-19 08:41:57 -08:00 committed by GitHub
parent 28078bf81c
commit 7159dd6867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 70 additions and 44 deletions

View File

@ -3,7 +3,7 @@ import log from 'loglevel'
import BN from 'bn.js' import BN from 'bn.js'
import createId from '../lib/random-id' import createId from '../lib/random-id'
import { bnToHex } from '../lib/util' import { bnToHex } from '../lib/util'
import fetchWithTimeout from '../lib/fetch-with-timeout' import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_CATEGORIES,
@ -24,9 +24,7 @@ import {
ROPSTEN_CHAIN_ID, ROPSTEN_CHAIN_ID,
} from './network/enums' } from './network/enums'
const fetch = fetchWithTimeout({ const fetchWithTimeout = getFetchWithTimeout(30000)
timeout: 30000,
})
/** /**
* This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check * This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check
@ -227,7 +225,7 @@ export default class IncomingTransactionsController {
if (fromBlock) { if (fromBlock) {
url += `&startBlock=${parseInt(fromBlock, 10)}` url += `&startBlock=${parseInt(fromBlock, 10)}`
} }
const response = await fetch(url) const response = await fetchWithTimeout(url)
const parsedResponse = await response.json() const parsedResponse = await response.json()
return { return {

View File

@ -2,6 +2,9 @@ import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel' import log from 'loglevel'
import { normalize as normalizeAddress } from 'eth-sig-util' import { normalize as normalizeAddress } from 'eth-sig-util'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
// By default, poll every 3 minutes // By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000 const DEFAULT_INTERVAL = 180 * 1000
@ -34,7 +37,7 @@ export default class TokenRatesController {
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}` const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
if (this._tokens.length > 0) { if (this._tokens.length > 0) {
try { try {
const response = await window.fetch( const response = await fetchWithTimeout(
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`, `https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`,
) )
const prices = await response.json() const prices = await response.json()

View File

@ -1,6 +1,9 @@
import extension from 'extensionizer' import extension from 'extensionizer'
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout'
import resolveEnsToIpfsContentId from './resolver' import resolveEnsToIpfsContentId from './resolver'
const fetchWithTimeout = getFetchWithTimeout(30000)
const supportedTopLevelDomains = ['eth'] const supportedTopLevelDomains = ['eth']
export default function setupEnsIpfsResolver({ export default function setupEnsIpfsResolver({
@ -55,7 +58,9 @@ export default function setupEnsIpfsResolver({
)}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}` )}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}`
try { try {
// check if ipfs gateway has result // check if ipfs gateway has result
const response = await window.fetch(resolvedUrl, { method: 'HEAD' }) const response = await fetchWithTimeout(resolvedUrl, {
method: 'HEAD',
})
if (response.status === 200) { if (response.status === 200) {
url = resolvedUrl url = resolvedUrl
} }

View File

@ -1,4 +1,7 @@
import log from 'loglevel' import log from 'loglevel'
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
const FIXTURE_SERVER_HOST = 'localhost' const FIXTURE_SERVER_HOST = 'localhost'
const FIXTURE_SERVER_PORT = 12345 const FIXTURE_SERVER_PORT = 12345
@ -24,7 +27,7 @@ export default class ReadOnlyNetworkStore {
*/ */
async _init() { async _init() {
try { try {
const response = await window.fetch(FIXTURE_SERVER_URL) const response = await fetchWithTimeout(FIXTURE_SERVER_URL)
if (response.ok) { if (response.ok) {
this._state = await response.json() this._state = await response.json()
} }

View File

@ -1,4 +1,10 @@
const fetchWithTimeout = ({ timeout = 120000 } = {}) => { import { memoize } from 'lodash'
const getFetchWithTimeout = memoize((timeout) => {
if (!Number.isInteger(timeout) || timeout < 1) {
throw new Error('Must specify positive integer timeout.')
}
return async function _fetch(url, opts) { return async function _fetch(url, opts) {
const abortController = new window.AbortController() const abortController = new window.AbortController()
const { signal } = abortController const { signal } = abortController
@ -18,6 +24,6 @@ const fetchWithTimeout = ({ timeout = 120000 } = {}) => {
throw e throw e
} }
} }
} })
export default fetchWithTimeout export default getFetchWithTimeout

View File

@ -1,14 +1,16 @@
import assert from 'assert' import assert from 'assert'
import nock from 'nock' import nock from 'nock'
import fetchWithTimeout from '../../../app/scripts/lib/fetch-with-timeout' import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
describe('fetchWithTimeout', function () { describe('getFetchWithTimeout', function () {
it('fetches a url', async function () { it('fetches a url', async function () {
nock('https://api.infura.io').get('/money').reply(200, '{"hodl": false}') nock('https://api.infura.io').get('/money').reply(200, '{"hodl": false}')
const fetch = fetchWithTimeout() const fetchWithTimeout = getFetchWithTimeout(30000)
const response = await (await fetch('https://api.infura.io/money')).json() const response = await (
await fetchWithTimeout('https://api.infura.io/money')
).json()
assert.deepEqual(response, { assert.deepEqual(response, {
hodl: false, hodl: false,
}) })
@ -20,12 +22,10 @@ describe('fetchWithTimeout', function () {
.delay(2000) .delay(2000)
.reply(200, '{"moon": "2012-12-21T11:11:11Z"}') .reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
const fetch = fetchWithTimeout({ const fetchWithTimeout = getFetchWithTimeout(123)
timeout: 123,
})
try { try {
await fetch('https://api.infura.io/moon').then((r) => r.json()) await fetchWithTimeout('https://api.infura.io/moon').then((r) => r.json())
assert.fail('Request should throw') assert.fail('Request should throw')
} catch (e) { } catch (e) {
assert.ok(e) assert.ok(e)
@ -38,15 +38,20 @@ describe('fetchWithTimeout', function () {
.delay(2000) .delay(2000)
.reply(200, '{"moon": "2012-12-21T11:11:11Z"}') .reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
const fetch = fetchWithTimeout({ const fetchWithTimeout = getFetchWithTimeout(123)
timeout: 123,
})
try { try {
await fetch('https://api.infura.io/moon').then((r) => r.json()) await fetchWithTimeout('https://api.infura.io/moon').then((r) => r.json())
assert.fail('Request should be aborted') assert.fail('Request should be aborted')
} catch (e) { } catch (e) {
assert.deepEqual(e.message, 'Aborted') assert.deepEqual(e.message, 'Aborted')
} }
}) })
it('throws on invalid timeout', async function () {
assert.throws(() => getFetchWithTimeout(), 'should throw')
assert.throws(() => getFetchWithTimeout(-1), 'should throw')
assert.throws(() => getFetchWithTimeout({}), 'should throw')
assert.throws(() => getFetchWithTimeout(true), 'should throw')
})
}) })

View File

@ -1,8 +1,10 @@
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers' import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers'
import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util' import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util'
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
// Actions // Actions
const BASIC_GAS_ESTIMATE_LOADING_FINISHED = const BASIC_GAS_ESTIMATE_LOADING_FINISHED =
@ -97,7 +99,7 @@ export function basicGasEstimatesLoadingFinished() {
async function basicGasPriceQuery() { async function basicGasPriceQuery() {
const url = `https://api.metaswap.codefi.network/gasPrices` const url = `https://api.metaswap.codefi.network/gasPrices`
return await window.fetch(url, { return await fetchWithTimeout(url, {
headers: {}, headers: {},
referrer: 'https://api.metaswap.codefi.network/gasPrices', referrer: 'https://api.metaswap.codefi.network/gasPrices',
referrerPolicy: 'no-referrer-when-downgrade', referrerPolicy: 'no-referrer-when-downgrade',

View File

@ -1,5 +1,5 @@
import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers' import { getStorageItem, setStorageItem } from '../../../lib/storage-helpers'
import fetchWithTimeout from '../../../../app/scripts/lib/fetch-with-timeout' import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout'
const fetchWithCache = async ( const fetchWithCache = async (
url, url,
@ -29,8 +29,8 @@ const fetchWithCache = async (
return cachedResponse return cachedResponse
} }
fetchOptions.headers.set('Content-Type', 'application/json') fetchOptions.headers.set('Content-Type', 'application/json')
const _fetch = timeout ? fetchWithTimeout({ timeout }) : window.fetch const fetchWithTimeout = getFetchWithTimeout(timeout)
const response = await _fetch(url, { const response = await fetchWithTimeout(url, {
referrerPolicy: 'no-referrer-when-downgrade', referrerPolicy: 'no-referrer-when-downgrade',
body: null, body: null,
method: 'GET', method: 'GET',

View File

@ -1,9 +1,12 @@
// cross-browser connection to extension i18n API // cross-browser connection to extension i18n API
import React from 'react' import React from 'react'
import log from 'loglevel' import log from 'loglevel'
import * as Sentry from '@sentry/browser' import * as Sentry from '@sentry/browser'
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
const warned = {} const warned = {}
const missingMessageErrors = {} const missingMessageErrors = {}
const missingSubstitutionErrors = {} const missingSubstitutionErrors = {}
@ -95,7 +98,7 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => {
export async function fetchLocale(localeCode) { export async function fetchLocale(localeCode) {
try { try {
const response = await window.fetch( const response = await fetchWithTimeout(
`./_locales/${localeCode}/messages.json`, `./_locales/${localeCode}/messages.json`,
) )
return await response.json() return await response.json()
@ -120,7 +123,7 @@ export async function loadRelativeTimeFormatLocaleData(localeCode) {
} }
async function fetchRelativeTimeFormatData(languageTag) { async function fetchRelativeTimeFormatData(languageTag) {
const response = await window.fetch( const response = await fetchWithTimeout(
`./intl/${languageTag}/relative-time-format-data.json`, `./intl/${languageTag}/relative-time-format-data.json`,
) )
return await response.json() return await response.json()

View File

@ -4,6 +4,9 @@ import BigNumber from 'bignumber.js'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import { addHexPrefix } from '../../../../app/scripts/lib/util' import { addHexPrefix } from '../../../../app/scripts/lib/util'
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
// formatData :: ( date: <Unix Timestamp> ) -> String // formatData :: ( date: <Unix Timestamp> ) -> String
export function formatDate(date, format = "M/d/y 'at' T") { export function formatDate(date, format = "M/d/y 'at' T") {
@ -478,19 +481,17 @@ export async function jsonRpcRequest(rpcUrl, rpcMethod, rpcParams = []) {
headers.Authorization = `Basic ${encodedAuth}` headers.Authorization = `Basic ${encodedAuth}`
fetchUrl = `${origin}${pathname}${search}` fetchUrl = `${origin}${pathname}${search}`
} }
const jsonRpcResponse = await window const jsonRpcResponse = await fetchWithTimeout(fetchUrl, {
.fetch(fetchUrl, { method: 'POST',
method: 'POST', body: JSON.stringify({
body: JSON.stringify({ id: Date.now().toString(),
id: Date.now().toString(), jsonrpc: '2.0',
jsonrpc: '2.0', method: rpcMethod,
method: rpcMethod, params: rpcParams,
params: rpcParams, }),
}), headers,
headers, cache: 'default',
cache: 'default', }).then((httpResponse) => httpResponse.json())
})
.then((httpResponse) => httpResponse.json())
if ( if (
!jsonRpcResponse || !jsonRpcResponse ||