Skip to content

Commit 0ef7f60

Browse files
pldespaignerekmarks
authored andcommitted
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
1 parent f49bc58 commit 0ef7f60

14 files changed

+205
-24
lines changed

app/_locales/en/messages.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,18 @@
735735
"invalidSeedPhrase": {
736736
"message": "Invalid seed phrase"
737737
},
738+
"ipfsGateway": {
739+
"message": "IPFS Gateway"
740+
},
741+
"ipfsGatewayDescription": {
742+
"message": "Enter the URL of the IPFS CID gateway to use for ENS content resolution."
743+
},
744+
"invalidIpfsGateway": {
745+
"message": "Invalid IPFS Gateway: The value must be a valid URL"
746+
},
747+
"forbiddenIpfsGateway": {
748+
"message": "Forbidden IPFS Gateway: Please specify a CID gateway"
749+
},
738750
"jsonFile": {
739751
"message": "JSON File",
740752
"description": "format for importing an account"
@@ -1336,7 +1348,7 @@
13361348
"message": "Sync data with 3Box (experimental)"
13371349
},
13381350
"syncWithThreeBoxDescription": {
1339-
"message": "Turn on to have your settings backed up with 3Box. This feature is currenty experimental; use at your own risk."
1351+
"message": "Turn on to have your settings backed up with 3Box. This feature is currently experimental; use at your own risk."
13401352
},
13411353
"syncWithThreeBoxDisabled": {
13421354
"message": "3Box has been disabled due to an error during the initial sync"
@@ -1360,7 +1372,7 @@
13601372
"message": "Make sure nobody else is looking at your screen when you scan this code"
13611373
},
13621374
"syncWithMobileComplete": {
1363-
"message": "Your data has been synced succesfully. Enjoy the MetaMask mobile app!"
1375+
"message": "Your data has been synced successfully. Enjoy the MetaMask mobile app!"
13641376
},
13651377
"terms": {
13661378
"message": "Terms of Use"

app/_locales/fr/messages.json

+12
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,18 @@
599599
"invalidSeedPhrase": {
600600
"message": "Phrase Seed invalide"
601601
},
602+
"ipfsGateway": {
603+
"message": "IPFS Gateway"
604+
},
605+
"ipfsGatewayDescription": {
606+
"message": "Entrez l'URL de la gateway CID IPFS à utiliser pour résoudre les contenus ENS."
607+
},
608+
"invalidIpfsGateway": {
609+
"message": "IPFS Gateway Invalide: la valeur doit être une URL valide"
610+
},
611+
"forbiddenIpfsGateway": {
612+
"message": "IPFS Gateway Interdite: veuillez spécifier une gateway CID"
613+
},
602614
"jsonFile": {
603615
"message": "Fichier JSON",
604616
"description": "format for importing an account"

app/scripts/background.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,10 @@ function setupController (initState, initLangCode) {
252252
},
253253
})
254254

255-
const provider = controller.provider
256-
setupEnsIpfsResolver({ provider })
255+
setupEnsIpfsResolver({
256+
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(controller.preferencesController),
257+
provider: controller.provider,
258+
})
257259

258260
// submit rpc requests to mesh-metrics
259261
controller.networkController.on('rpc-req', (data) => {

app/scripts/controllers/preferences.js

+21
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class PreferencesController {
6060
completedOnboarding: false,
6161
metaMetricsId: null,
6262
metaMetricsSendCount: 0,
63+
64+
// ENS decentralized website resolution
65+
ipfsGateway: 'ipfs.dweb.link',
6366
}, opts.initState)
6467

6568
this.diagnostics = opts.diagnostics
@@ -608,6 +611,24 @@ class PreferencesController {
608611
return Promise.resolve(true)
609612
}
610613

614+
/**
615+
* A getter for the `ipfsGateway` property
616+
* @returns {string} The current IPFS gateway domain
617+
*/
618+
getIpfsGateway () {
619+
return this.store.getState().ipfsGateway
620+
}
621+
622+
/**
623+
* A setter for the `ipfsGateway` property
624+
* @param {string} domain The new IPFS gateway domain
625+
* @returns {Promise<string>} A promise of the update IPFS gateway domain
626+
*/
627+
setIpfsGateway (domain) {
628+
this.store.updateState({ ipfsGateway: domain })
629+
return Promise.resolve(domain)
630+
}
631+
611632
//
612633
// PRIVATE METHODS
613634
//

app/scripts/lib/ens-ipfs/resolver.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,13 @@ async function resolveEnsToIpfsContentId ({ provider, name }) {
3232
if (isEIP1577Compliant[0]) {
3333
const contentLookupResult = await Resolver.contenthash(hash)
3434
const rawContentHash = contentLookupResult[0]
35-
const decodedContentHash = contentHash.decode(rawContentHash)
35+
let decodedContentHash = contentHash.decode(rawContentHash)
3636
const type = contentHash.getCodec(rawContentHash)
37+
38+
if (type === 'ipfs-ns') {
39+
decodedContentHash = contentHash.helpers.cidV0ToV1Base32(decodedContentHash)
40+
}
41+
3742
return { type: type, hash: decodedContentHash }
3843
}
3944
if (isLegacyResolver[0]) {

app/scripts/lib/ens-ipfs/setup.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const supportedTopLevelDomains = ['eth']
66

77
module.exports = setupEnsIpfsResolver
88

9-
function setupEnsIpfsResolver ({ provider }) {
9+
function setupEnsIpfsResolver ({ provider, getIpfsGateway }) {
1010

1111
// install listener
1212
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`)
@@ -40,12 +40,13 @@ function setupEnsIpfsResolver ({ provider }) {
4040
}
4141

4242
async function attemptResolve ({ tabId, name, path, search, fragment }) {
43+
const ipfsGateway = getIpfsGateway()
4344
extension.tabs.update(tabId, { url: `loading.html` })
4445
let url = `https://app.ens.domains/name/${name}`
4546
try {
4647
const { type, hash } = await resolveEnsToIpfsContentId({ provider, name })
4748
if (type === 'ipfs-ns') {
48-
const resolvedUrl = `https://gateway.ipfs.io/ipfs/${hash}${path}${search || ''}${fragment || ''}`
49+
const resolvedUrl = `https://${hash}.${ipfsGateway}${path}${search || ''}${fragment || ''}`
4950
try {
5051
// check if ipfs gateway has result
5152
const response = await fetch(resolvedUrl, { method: 'HEAD' })

app/scripts/metamask-controller.js

+15
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ module.exports = class MetamaskController extends EventEmitter {
439439
setCurrentCurrency: this.setCurrentCurrency.bind(this),
440440
setUseBlockie: this.setUseBlockie.bind(this),
441441
setUseNonceField: this.setUseNonceField.bind(this),
442+
setIpfsGateway: this.setIpfsGateway.bind(this),
442443
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
443444
setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this),
444445
setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this),
@@ -1841,6 +1842,20 @@ module.exports = class MetamaskController extends EventEmitter {
18411842
}
18421843
}
18431844

1845+
/**
1846+
* Sets the IPFS gateway to use for ENS content resolution.
1847+
* @param {string} val - the host of the gateway to set
1848+
* @param {Function} cb - A callback function called when complete.
1849+
*/
1850+
setIpfsGateway (val, cb) {
1851+
try {
1852+
this.preferencesController.setIpfsGateway(val)
1853+
cb(null)
1854+
} catch (err) {
1855+
cb(err)
1856+
}
1857+
}
1858+
18441859
/**
18451860
* Sets whether or not the user will have usage data tracked with MetaMetrics
18461861
* @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"c3": "^0.6.7",
7676
"classnames": "^2.2.5",
7777
"clone": "^2.1.2",
78-
"content-hash": "^2.4.4",
78+
"content-hash": "^2.5.0",
7979
"copy-to-clipboard": "^3.0.8",
8080
"currency-formatter": "^1.4.2",
8181
"d3": "^5.7.0",

ui/app/pages/settings/advanced-tab/advanced-tab.component.js

+93
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ export default class AdvancedTab extends PureComponent {
3131
threeBoxSyncingAllowed: PropTypes.bool.isRequired,
3232
setThreeBoxSyncingPermission: PropTypes.func.isRequired,
3333
threeBoxDisabled: PropTypes.bool.isRequired,
34+
setIpfsGateway: PropTypes.func.isRequired,
35+
ipfsGateway: PropTypes.string.isRequired,
3436
}
3537

3638
state = {
3739
autoLogoutTimeLimit: this.props.autoLogoutTimeLimit,
3840
logoutTimeError: '',
41+
ipfsGateway: this.props.ipfsGateway,
42+
ipfsGatewayError: '',
3943
}
4044

4145
renderMobileSync () {
@@ -354,6 +358,85 @@ export default class AdvancedTab extends PureComponent {
354358
)
355359
}
356360

361+
handleIpfsGatewayChange (url) {
362+
const { t } = this.context
363+
364+
this.setState(() => {
365+
let ipfsGatewayError = ''
366+
367+
try {
368+
369+
const urlObj = new URL(addUrlProtocolPrefix(url))
370+
if (!urlObj.host) {
371+
throw new Error()
372+
}
373+
374+
// don't allow the use of this gateway
375+
if (urlObj.host === 'gateway.ipfs.io') {
376+
throw new Error('Forbidden gateway')
377+
}
378+
379+
} catch (error) {
380+
ipfsGatewayError = (
381+
error.message === 'Forbidden gateway'
382+
? t('forbiddenIpfsGateway')
383+
: t('invalidIpfsGateway')
384+
)
385+
}
386+
387+
return {
388+
ipfsGateway: url,
389+
ipfsGatewayError,
390+
}
391+
})
392+
}
393+
394+
handleIpfsGatewaySave () {
395+
396+
const url = new URL(addUrlProtocolPrefix(this.state.ipfsGateway))
397+
const host = url.host
398+
399+
this.props.setIpfsGateway(host)
400+
}
401+
402+
renderIpfsGatewayControl () {
403+
const { t } = this.context
404+
const { ipfsGatewayError } = this.state
405+
406+
return (
407+
<div className="settings-page__content-row">
408+
<div className="settings-page__content-item">
409+
<span>{ t('ipfsGateway') }</span>
410+
<div className="settings-page__content-description">
411+
{ t('ipfsGatewayDescription') }
412+
</div>
413+
</div>
414+
<div className="settings-page__content-item">
415+
<div className="settings-page__content-item-col">
416+
<TextField
417+
type="text"
418+
value={this.state.ipfsGateway}
419+
onChange={e => this.handleIpfsGatewayChange(e.target.value)}
420+
error={ipfsGatewayError}
421+
fullWidth
422+
margin="dense"
423+
/>
424+
<Button
425+
type="primary"
426+
className="settings-tab__rpc-save-button"
427+
disabled={Boolean(ipfsGatewayError)}
428+
onClick={() => {
429+
this.handleIpfsGatewaySave()
430+
}}
431+
>
432+
{ t('save') }
433+
</Button>
434+
</div>
435+
</div>
436+
</div>
437+
)
438+
}
439+
357440
renderContent () {
358441
const { warning } = this.props
359442

@@ -369,6 +452,7 @@ export default class AdvancedTab extends PureComponent {
369452
{ this.renderUseNonceOptIn() }
370453
{ this.renderAutoLogoutTimeLimit() }
371454
{ this.renderThreeBoxControl() }
455+
{ this.renderIpfsGatewayControl() }
372456
</div>
373457
)
374458
}
@@ -377,3 +461,12 @@ export default class AdvancedTab extends PureComponent {
377461
return this.renderContent()
378462
}
379463
}
464+
465+
function addUrlProtocolPrefix (urlString) {
466+
if (!urlString.match(
467+
/(^http:\/\/)|(^https:\/\/)/
468+
)) {
469+
return 'https://' + urlString
470+
}
471+
return urlString
472+
}

ui/app/pages/settings/advanced-tab/advanced-tab.container.js

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
setThreeBoxSyncingPermission,
1212
turnThreeBoxSyncingOnAndInitialize,
1313
setUseNonceField,
14+
setIpfsGateway,
1415
} from '../../../store/actions'
1516
import { preferencesSelector } from '../../../selectors/selectors'
1617

@@ -24,6 +25,7 @@ export const mapStateToProps = state => {
2425
threeBoxSyncingAllowed,
2526
threeBoxDisabled,
2627
useNonceField,
28+
ipfsGateway,
2729
} = metamask
2830
const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
2931

@@ -36,6 +38,7 @@ export const mapStateToProps = state => {
3638
threeBoxSyncingAllowed,
3739
threeBoxDisabled,
3840
useNonceField,
41+
ipfsGateway,
3942
}
4043
}
4144

@@ -59,6 +62,9 @@ export const mapDispatchToProps = dispatch => {
5962
dispatch(setThreeBoxSyncingPermission(newThreeBoxSyncingState))
6063
}
6164
},
65+
setIpfsGateway: value => {
66+
return dispatch(setIpfsGateway(value))
67+
},
6268
}
6369
}
6470

ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('AdvancedTab Component', () => {
1616
}
1717
)
1818

19-
assert.equal(root.find('.settings-page__content-row').length, 9)
19+
assert.equal(root.find('.settings-page__content-row').length, 10)
2020
})
2121

2222
it('should update autoLogoutTimeLimit', () => {

ui/app/selectors/selectors.js

+4
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,7 @@ export function getLastConnectedInfo (state) {
543543
}, {})
544544
return lastConnectedInfoData
545545
}
546+
547+
export function getIpfsGateway (state) {
548+
return state.metamask.ipfsGateway
549+
}

ui/app/store/actions.js

+20
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ const actions = {
304304
setUseNonceField,
305305
UPDATE_CUSTOM_NONCE: 'UPDATE_CUSTOM_NONCE',
306306
updateCustomNonce,
307+
SET_IPFS_GATEWAY: 'SET_IPFS_GATEWAY',
308+
setIpfsGateway,
307309

308310
SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS',
309311
SET_METAMETRICS_SEND_COUNT: 'SET_METAMETRICS_SEND_COUNT',
@@ -2660,6 +2662,24 @@ function setUseNonceField (val) {
26602662
}
26612663
}
26622664

2665+
function setIpfsGateway (val) {
2666+
return (dispatch) => {
2667+
dispatch(actions.showLoadingIndication())
2668+
log.debug(`background.setIpfsGateway`)
2669+
background.setIpfsGateway(val, (err) => {
2670+
dispatch(actions.hideLoadingIndication())
2671+
if (err) {
2672+
return dispatch(actions.displayWarning(err.message))
2673+
} else {
2674+
dispatch({
2675+
type: actions.SET_IPFS_GATEWAY,
2676+
value: val,
2677+
})
2678+
}
2679+
})
2680+
}
2681+
}
2682+
26632683
function updateCurrentLocale (key) {
26642684
return (dispatch) => {
26652685
dispatch(actions.showLoadingIndication())

0 commit comments

Comments
 (0)