Ipfs cid v1 base32 (#7362)

add ipfs gateway to advanced settings
use ipfs gateway from settings
use ipfs.dweb.link as default CID gateway
disallow gateway.ipfs.io as gateway
feature/default_network_editable
pldespaigne 5 years ago committed by Erik Marks
parent f49bc58c09
commit 0ef7f603d6
  1. 16
      app/_locales/en/messages.json
  2. 12
      app/_locales/fr/messages.json
  3. 6
      app/scripts/background.js
  4. 21
      app/scripts/controllers/preferences.js
  5. 7
      app/scripts/lib/ens-ipfs/resolver.js
  6. 5
      app/scripts/lib/ens-ipfs/setup.js
  7. 15
      app/scripts/metamask-controller.js
  8. 2
      package.json
  9. 93
      ui/app/pages/settings/advanced-tab/advanced-tab.component.js
  10. 6
      ui/app/pages/settings/advanced-tab/advanced-tab.container.js
  11. 2
      ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
  12. 4
      ui/app/selectors/selectors.js
  13. 20
      ui/app/store/actions.js
  14. 20
      yarn.lock

@ -735,6 +735,18 @@
"invalidSeedPhrase": {
"message": "Invalid seed phrase"
},
"ipfsGateway": {
"message": "IPFS Gateway"
},
"ipfsGatewayDescription": {
"message": "Enter the URL of the IPFS CID gateway to use for ENS content resolution."
},
"invalidIpfsGateway": {
"message": "Invalid IPFS Gateway: The value must be a valid URL"
},
"forbiddenIpfsGateway": {
"message": "Forbidden IPFS Gateway: Please specify a CID gateway"
},
"jsonFile": {
"message": "JSON File",
"description": "format for importing an account"
@ -1336,7 +1348,7 @@
"message": "Sync data with 3Box (experimental)"
},
"syncWithThreeBoxDescription": {
"message": "Turn on to have your settings backed up with 3Box. This feature is currenty experimental; use at your own risk."
"message": "Turn on to have your settings backed up with 3Box. This feature is currently experimental; use at your own risk."
},
"syncWithThreeBoxDisabled": {
"message": "3Box has been disabled due to an error during the initial sync"
@ -1360,7 +1372,7 @@
"message": "Make sure nobody else is looking at your screen when you scan this code"
},
"syncWithMobileComplete": {
"message": "Your data has been synced succesfully. Enjoy the MetaMask mobile app!"
"message": "Your data has been synced successfully. Enjoy the MetaMask mobile app!"
},
"terms": {
"message": "Terms of Use"

@ -599,6 +599,18 @@
"invalidSeedPhrase": {
"message": "Phrase Seed invalide"
},
"ipfsGateway": {
"message": "IPFS Gateway"
},
"ipfsGatewayDescription": {
"message": "Entrez l'URL de la gateway CID IPFS à utiliser pour résoudre les contenus ENS."
},
"invalidIpfsGateway": {
"message": "IPFS Gateway Invalide: la valeur doit être une URL valide"
},
"forbiddenIpfsGateway": {
"message": "IPFS Gateway Interdite: veuillez spécifier une gateway CID"
},
"jsonFile": {
"message": "Fichier JSON",
"description": "format for importing an account"

@ -252,8 +252,10 @@ function setupController (initState, initLangCode) {
},
})
const provider = controller.provider
setupEnsIpfsResolver({ provider })
setupEnsIpfsResolver({
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(controller.preferencesController),
provider: controller.provider,
})
// submit rpc requests to mesh-metrics
controller.networkController.on('rpc-req', (data) => {

@ -60,6 +60,9 @@ class PreferencesController {
completedOnboarding: false,
metaMetricsId: null,
metaMetricsSendCount: 0,
// ENS decentralized website resolution
ipfsGateway: 'ipfs.dweb.link',
}, opts.initState)
this.diagnostics = opts.diagnostics
@ -608,6 +611,24 @@ class PreferencesController {
return Promise.resolve(true)
}
/**
* A getter for the `ipfsGateway` property
* @returns {string} The current IPFS gateway domain
*/
getIpfsGateway () {
return this.store.getState().ipfsGateway
}
/**
* A setter for the `ipfsGateway` property
* @param {string} domain The new IPFS gateway domain
* @returns {Promise<string>} A promise of the update IPFS gateway domain
*/
setIpfsGateway (domain) {
this.store.updateState({ ipfsGateway: domain })
return Promise.resolve(domain)
}
//
// PRIVATE METHODS
//

@ -32,8 +32,13 @@ async function resolveEnsToIpfsContentId ({ provider, name }) {
if (isEIP1577Compliant[0]) {
const contentLookupResult = await Resolver.contenthash(hash)
const rawContentHash = contentLookupResult[0]
const decodedContentHash = contentHash.decode(rawContentHash)
let decodedContentHash = contentHash.decode(rawContentHash)
const type = contentHash.getCodec(rawContentHash)
if (type === 'ipfs-ns') {
decodedContentHash = contentHash.helpers.cidV0ToV1Base32(decodedContentHash)
}
return { type: type, hash: decodedContentHash }
}
if (isLegacyResolver[0]) {

@ -6,7 +6,7 @@ const supportedTopLevelDomains = ['eth']
module.exports = setupEnsIpfsResolver
function setupEnsIpfsResolver ({ provider }) {
function setupEnsIpfsResolver ({ provider, getIpfsGateway }) {
// install listener
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`)
@ -40,12 +40,13 @@ function setupEnsIpfsResolver ({ provider }) {
}
async function attemptResolve ({ tabId, name, path, search, fragment }) {
const ipfsGateway = getIpfsGateway()
extension.tabs.update(tabId, { url: `loading.html` })
let url = `https://app.ens.domains/name/${name}`
try {
const { type, hash } = await resolveEnsToIpfsContentId({ provider, name })
if (type === 'ipfs-ns') {
const resolvedUrl = `https://gateway.ipfs.io/ipfs/${hash}${path}${search || ''}${fragment || ''}`
const resolvedUrl = `https://${hash}.${ipfsGateway}${path}${search || ''}${fragment || ''}`
try {
// check if ipfs gateway has result
const response = await fetch(resolvedUrl, { method: 'HEAD' })

@ -439,6 +439,7 @@ module.exports = class MetamaskController extends EventEmitter {
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setUseBlockie: this.setUseBlockie.bind(this),
setUseNonceField: this.setUseNonceField.bind(this),
setIpfsGateway: this.setIpfsGateway.bind(this),
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this),
setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this),
@ -1841,6 +1842,20 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* Sets the IPFS gateway to use for ENS content resolution.
* @param {string} val - the host of the gateway to set
* @param {Function} cb - A callback function called when complete.
*/
setIpfsGateway (val, cb) {
try {
this.preferencesController.setIpfsGateway(val)
cb(null)
} catch (err) {
cb(err)
}
}
/**
* Sets whether or not the user will have usage data tracked with MetaMetrics
* @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out.

@ -75,7 +75,7 @@
"c3": "^0.6.7",
"classnames": "^2.2.5",
"clone": "^2.1.2",
"content-hash": "^2.4.4",
"content-hash": "^2.5.0",
"copy-to-clipboard": "^3.0.8",
"currency-formatter": "^1.4.2",
"d3": "^5.7.0",

@ -31,11 +31,15 @@ export default class AdvancedTab extends PureComponent {
threeBoxSyncingAllowed: PropTypes.bool.isRequired,
setThreeBoxSyncingPermission: PropTypes.func.isRequired,
threeBoxDisabled: PropTypes.bool.isRequired,
setIpfsGateway: PropTypes.func.isRequired,
ipfsGateway: PropTypes.string.isRequired,
}
state = {
autoLogoutTimeLimit: this.props.autoLogoutTimeLimit,
logoutTimeError: '',
ipfsGateway: this.props.ipfsGateway,
ipfsGatewayError: '',
}
renderMobileSync () {
@ -354,6 +358,85 @@ export default class AdvancedTab extends PureComponent {
)
}
handleIpfsGatewayChange (url) {
const { t } = this.context
this.setState(() => {
let ipfsGatewayError = ''
try {
const urlObj = new URL(addUrlProtocolPrefix(url))
if (!urlObj.host) {
throw new Error()
}
// don't allow the use of this gateway
if (urlObj.host === 'gateway.ipfs.io') {
throw new Error('Forbidden gateway')
}
} catch (error) {
ipfsGatewayError = (
error.message === 'Forbidden gateway'
? t('forbiddenIpfsGateway')
: t('invalidIpfsGateway')
)
}
return {
ipfsGateway: url,
ipfsGatewayError,
}
})
}
handleIpfsGatewaySave () {
const url = new URL(addUrlProtocolPrefix(this.state.ipfsGateway))
const host = url.host
this.props.setIpfsGateway(host)
}
renderIpfsGatewayControl () {
const { t } = this.context
const { ipfsGatewayError } = this.state
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('ipfsGateway') }</span>
<div className="settings-page__content-description">
{ t('ipfsGatewayDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
value={this.state.ipfsGateway}
onChange={e => this.handleIpfsGatewayChange(e.target.value)}
error={ipfsGatewayError}
fullWidth
margin="dense"
/>
<Button
type="primary"
className="settings-tab__rpc-save-button"
disabled={Boolean(ipfsGatewayError)}
onClick={() => {
this.handleIpfsGatewaySave()
}}
>
{ t('save') }
</Button>
</div>
</div>
</div>
)
}
renderContent () {
const { warning } = this.props
@ -369,6 +452,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderUseNonceOptIn() }
{ this.renderAutoLogoutTimeLimit() }
{ this.renderThreeBoxControl() }
{ this.renderIpfsGatewayControl() }
</div>
)
}
@ -377,3 +461,12 @@ export default class AdvancedTab extends PureComponent {
return this.renderContent()
}
}
function addUrlProtocolPrefix (urlString) {
if (!urlString.match(
/(^http:\/\/)|(^https:\/\/)/
)) {
return 'https://' + urlString
}
return urlString
}

@ -11,6 +11,7 @@ import {
setThreeBoxSyncingPermission,
turnThreeBoxSyncingOnAndInitialize,
setUseNonceField,
setIpfsGateway,
} from '../../../store/actions'
import { preferencesSelector } from '../../../selectors/selectors'
@ -24,6 +25,7 @@ export const mapStateToProps = state => {
threeBoxSyncingAllowed,
threeBoxDisabled,
useNonceField,
ipfsGateway,
} = metamask
const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
@ -36,6 +38,7 @@ export const mapStateToProps = state => {
threeBoxSyncingAllowed,
threeBoxDisabled,
useNonceField,
ipfsGateway,
}
}
@ -59,6 +62,9 @@ export const mapDispatchToProps = dispatch => {
dispatch(setThreeBoxSyncingPermission(newThreeBoxSyncingState))
}
},
setIpfsGateway: value => {
return dispatch(setIpfsGateway(value))
},
}
}

@ -16,7 +16,7 @@ describe('AdvancedTab Component', () => {
}
)
assert.equal(root.find('.settings-page__content-row').length, 9)
assert.equal(root.find('.settings-page__content-row').length, 10)
})
it('should update autoLogoutTimeLimit', () => {

@ -543,3 +543,7 @@ export function getLastConnectedInfo (state) {
}, {})
return lastConnectedInfoData
}
export function getIpfsGateway (state) {
return state.metamask.ipfsGateway
}

@ -304,6 +304,8 @@ const actions = {
setUseNonceField,
UPDATE_CUSTOM_NONCE: 'UPDATE_CUSTOM_NONCE',
updateCustomNonce,
SET_IPFS_GATEWAY: 'SET_IPFS_GATEWAY',
setIpfsGateway,
SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS',
SET_METAMETRICS_SEND_COUNT: 'SET_METAMETRICS_SEND_COUNT',
@ -2660,6 +2662,24 @@ function setUseNonceField (val) {
}
}
function setIpfsGateway (val) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setIpfsGateway`)
background.setIpfsGateway(val, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
} else {
dispatch({
type: actions.SET_IPFS_GATEWAY,
value: val,
})
}
})
}
}
function updateCurrentLocale (key) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())

@ -6826,16 +6826,6 @@ cids@^0.5.3, cids@~0.5.4, cids@~0.5.6:
multicodec "~0.5.0"
multihashes "~0.4.14"
cids@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cids/-/cids-0.6.0.tgz#0de7056a5246a7c7ebf3134eb4d83b3b8b841a06"
integrity sha512-34wuIeiBZOuvBwUuYR4XooVuXUQI2PYU9VmgM2eB3xkSmQYRlv2kh/dIbmGiLY2GuONlGR3lLtYdVkx1G9yXUg==
dependencies:
class-is "^1.1.0"
multibase "~0.6.0"
multicodec "~0.5.0"
multihashes "~0.4.14"
cids@^0.7.1, cids@~0.7.0, cids@~0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.1.tgz#d8bba49a35a0e82110879b5001abf1039c62347f"
@ -7413,12 +7403,12 @@ content-disposition@0.5.3, content-disposition@^0.5.2, content-disposition@~0.5.
dependencies:
safe-buffer "5.1.2"
content-hash@^2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.4.4.tgz#4bec87caecfcff8cf1a37645301cbef4728a083f"
integrity sha512-3FaUsqt7VR725pVxe0vIScGI5efmpryIXdVSeXafQ63fb5gRGparAQlAGxTSOiv0yRg7YeliseXuB20ByD1duQ==
content-hash@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.5.0.tgz#6f8a98feec0360f09213e951c69e57c727093e3a"
integrity sha512-Opn9YSjQQc3Wst/VPwGTdKcNONuQr4yc0dtvEbV82UGDIsc7dE2bdTz3FpLRxfnuOkHx8y9/94sZ3aytmUQ1HA==
dependencies:
cids "^0.6.0"
cids "^0.7.1"
multicodec "^0.5.5"
multihashes "^0.4.15"

Loading…
Cancel
Save