Skip to content

Commit

Permalink
Feature: Choose IPFS gateway (#592)
Browse files Browse the repository at this point in the history
* use of ipfsgateway from GABA

* handle ipfs gateway selection

* snapshots

* snapshots

* reorder ipfs gateways list

* only show available ipfs gateways

* snapshots

* bump gaba

* handle current gateway down

* snapshots
  • Loading branch information
estebanmino authored Apr 10, 2019
1 parent 61275e2 commit afaac75
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 12 deletions.
10 changes: 9 additions & 1 deletion app/components/UI/SelectComponent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ const styles = StyleSheet.create({

export default class SelectComponent extends Component {
static propTypes = {
/**
* Default value to show
*/
defaultValue: PropTypes.string,
/**
* Label for the field
*/
Expand Down Expand Up @@ -137,10 +141,14 @@ export default class SelectComponent extends Component {
};

getSelectedValue = () => {
const el = this.props.options && this.props.options.filter(o => o.value === this.props.selectedValue);
const { options, selectedValue, defaultValue } = this.props;
const el = options && options.filter(o => o.value === selectedValue);
if (el.length && el[0].label) {
return el[0].label;
}
if (defaultValue) {
return defaultValue;
}
return '';
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,61 @@ exports[`AdvancedSettings should render correctly 1`] = `
</View>
</View>
</View>
<View
style={
Array [
Object {
"marginTop": 50,
},
]
}
>
<Text
style={
Object {
"color": "#000000",
"fontFamily": "Roboto",
"fontSize": 20,
"fontWeight": "400",
"lineHeight": 20,
}
}
>
IPFS Gateway
</Text>
<Text
style={
Object {
"color": "#4d4d4d",
"fontFamily": "Roboto",
"fontSize": 14,
"fontWeight": "400",
"lineHeight": 20,
"marginTop": 12,
}
}
>
Choose your preferred IPFS gateway.
</Text>
<View
style={
Object {
"borderColor": "#d2d8dd",
"borderRadius": 5,
"borderWidth": 2,
"marginTop": 16,
}
}
>
<SelectComponent
defaultValue="Your current IPFS gateway is down"
label="IPFS Gateway"
onValueChange={[Function]}
options={Array []}
selectedValue="https://ipfs.io/ipfs/"
/>
</View>
</View>
<View
style={
Object {
Expand Down
66 changes: 62 additions & 4 deletions app/components/Views/AdvancedSettings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ import { Buffer } from 'buffer';
import Logger from '../../../util/Logger';
import { isprivateConnection } from '../../../util/networks';
import URL from 'url-parse';
import ipfsGateways from '../../../util/ipfs-gateways.json';
import SelectComponent from '../../UI/SelectComponent';
import timeoutFetch from '../../../util/general';

const HASH_TO_TEST = 'Qmaisz6NMhDB51cCvNWa1GMS7LU1pAxdF4Ld6Ft9kZEP2a';
const HASH_STRING = 'Hello from IPFS Gateway Checker';

const styles = StyleSheet.create({
wrapper: {
Expand Down Expand Up @@ -103,6 +109,12 @@ const styles = StyleSheet.create({
textAlign: 'center',
marginBottom: 20
},
picker: {
borderColor: colors.lightGray,
borderRadius: 5,
borderWidth: 2,
marginTop: 16
},
inner: {
paddingBottom: 48
},
Expand All @@ -120,6 +132,10 @@ const styles = StyleSheet.create({
*/
class AdvancedSettings extends Component {
static propTypes = {
/**
* A string that of the chosen ipfs gateway
*/
ipfsGateway: PropTypes.string,
/**
/* navigation object required to push new views
*/
Expand All @@ -145,10 +161,12 @@ class AdvancedSettings extends Component {
resetModalVisible: false,
rpcUrl: undefined,
warningRpcUrl: '',
inputWidth: Platform.OS === 'android' ? '99%' : undefined
inputWidth: Platform.OS === 'android' ? '99%' : undefined,
onlineIpfsGateways: []
};

componentDidMount = () => {
componentDidMount = async () => {
await this.handleAvailableIpfsGateways();
this.mounted = true;
// Workaround https://github.com/facebook/react-native/issues/9958
this.state.inputWidth &&
Expand All @@ -161,6 +179,26 @@ class AdvancedSettings extends Component {
this.mounted = false;
};

handleAvailableIpfsGateways = async () => {
const ipfsGatewaysPromises = ipfsGateways.map(async ipfsGateway => {
const testUrl = ipfsGateway.value + HASH_TO_TEST + '#x-ipfs-companion-no-redirect';
try {
const res = await timeoutFetch(testUrl);
const text = await res.text();
const available = text.trim() === HASH_STRING.trim();
ipfsGateway.available = available;
return ipfsGateway;
} catch (e) {
ipfsGateway.available = false;
return ipfsGateway;
}
});
const ipfsGatewaysAvailability = await Promise.all(ipfsGatewaysPromises);
const onlineIpfsGateways = ipfsGatewaysAvailability.filter(ipfsGateway => ipfsGateway.available);
const sortedOnlineIpfsGateways = onlineIpfsGateways.sort((a, b) => a.key < b.key);
this.setState({ onlineIpfsGateways: sortedOnlineIpfsGateways });
};

displayResetAccountModal = () => {
this.setState({ resetModalVisible: true });
};
Expand Down Expand Up @@ -258,9 +296,14 @@ class AdvancedSettings extends Component {
}
};

setIpfsGateway = ipfsGateway => {
const { PreferencesController } = Engine.context;
PreferencesController.setIpfsGateway(ipfsGateway);
};

render = () => {
const { showHexData } = this.props;
const { resetModalVisible } = this.state;
const { showHexData, ipfsGateway } = this.props;
const { resetModalVisible, onlineIpfsGateways } = this.state;
return (
<SafeAreaView style={baseStyles.flexGrow}>
<KeyboardAwareScrollView style={styles.wrapper} resetScrollToCoords={{ x: 0, y: 0 }}>
Expand Down Expand Up @@ -330,6 +373,20 @@ class AdvancedSettings extends Component {
</View>
</View>
</View>

<View style={[styles.setting]}>
<Text style={styles.title}>{strings('app_settings.ipfs_gateway')}</Text>
<Text style={styles.desc}>{strings('app_settings.ipfs_gateway_desc')}</Text>
<View style={styles.picker}>
<SelectComponent
selectedValue={ipfsGateway}
defaultValue={strings('app_settings.ipfs_gateway_down')}
onValueChange={this.setIpfsGateway}
label={strings('app_settings.ipfs_gateway')}
options={onlineIpfsGateways}
/>
</View>
</View>
<View style={styles.setting}>
<Text style={styles.title}>{strings('app_settings.show_hex_data')}</Text>
<Text style={styles.desc}>{strings('app_settings.hex_desc')}</Text>
Expand Down Expand Up @@ -363,6 +420,7 @@ class AdvancedSettings extends Component {
}

const mapStateToProps = state => ({
ipfsGateway: state.engine.backgroundState.PreferencesController.ipfsGateway,
showHexData: state.settings.showHexData,
fullState: state
});
Expand Down
9 changes: 8 additions & 1 deletion app/components/Views/AdvancedSettings/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ describe('AdvancedSettings', () => {

it('should render correctly', () => {
const initialState = {
settings: { showHexData: true }
settings: { showHexData: true },
engine: {
backgroundState: {
PreferencesController: {
ipfsGateway: 'https://ipfs.io/ipfs/'
}
}
}
};

const wrapper = shallow(
Expand Down
15 changes: 12 additions & 3 deletions app/components/Views/Browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ export class Browser extends Component {
* Protocol string to append to URLs that have none
*/
defaultProtocol: PropTypes.string,
/**
* A string that of the chosen ipfs gateway
*/
ipfsGateway: PropTypes.string,
/**
* Object containing the information for the current transaction
*/
Expand Down Expand Up @@ -690,6 +694,7 @@ export class Browser extends Component {
const sanitizedURL = hasProtocol ? url : `${this.props.defaultProtocol}${url}`;
const urlObj = new URL(sanitizedURL);
const { hostname, query, pathname } = urlObj;
const { ipfsGateway } = this.props;

let ipfsContent = null;
let currentEnsName = null;
Expand All @@ -702,7 +707,7 @@ export class Browser extends Component {
const urlObj = new URL(sanitizedURL);
currentEnsName = urlObj.hostname;
ipfsHash = ipfsContent
.replace(this.state.ipfsGateway, '')
.replace(ipfsGateway, '')
.split('/')
.shift();
}
Expand Down Expand Up @@ -756,6 +761,8 @@ export class Browser extends Component {

async handleIpfsContent(fullUrl, { hostname, pathname, query }) {
const { provider } = Engine.context.NetworkController;
const { ipfsGateway } = this.props;

let ipfsHash;
try {
ipfsHash = await resolveEnsToIpfsContentId({ provider, name: hostname });
Expand All @@ -766,7 +773,7 @@ export class Browser extends Component {
return null;
}

const gatewayUrl = `${this.state.ipfsGateway}${ipfsHash}${pathname || '/'}${query || ''}`;
const gatewayUrl = `${ipfsGateway}${ipfsHash}${pathname || '/'}${query || ''}`;

try {
const response = await fetch(gatewayUrl, { method: 'HEAD' });
Expand Down Expand Up @@ -1025,6 +1032,7 @@ export class Browser extends Component {
}

onPageChange = ({ url }) => {
const { ipfsGateway } = this.props;
if ((this.goingBack && url === 'about:blank') || (this.initialUrl === url && url === 'about:blank')) {
this.goBackToHomepage();
return;
Expand All @@ -1044,7 +1052,7 @@ export class Browser extends Component {
data.inputValue = url;
} else if (url.search(`${AppConstants.IPFS_OVERRIDE_PARAM}=false`) === -1) {
data.inputValue = url.replace(
`${this.state.ipfsGateway}${this.state.ipfsHash}/`,
`${ipfsGateway}${this.state.ipfsHash}/`,
`https://${this.state.currentEnsName}/`
);
} else if (this.isENSUrl(url)) {
Expand Down Expand Up @@ -1661,6 +1669,7 @@ export class Browser extends Component {
const mapStateToProps = state => ({
approvedHosts: state.privacy.approvedHosts,
bookmarks: state.bookmarks,
ipfsGateway: state.engine.backgroundState.PreferencesController.ipfsGateway,
networkType: state.engine.backgroundState.NetworkController.provider.type,
network: state.engine.backgroundState.NetworkController.network,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ exports[`Settings should render correctly 1`] = `
title="General"
/>
<SettingsDrawer
description="Access developer features, reset account, setup testnets, sync with extension, state logs and custom RPC"
description="Access developer features, reset account, setup testnets, sync with extension, state logs, IPFS gateway and custom RPC"
onPress={[Function]}
title="Advanced"
/>
Expand Down
15 changes: 15 additions & 0 deletions app/util/general.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Fetch that fails after timeout
*
* @param url - Url to fetch
* @param options - Options to send with the request
* @param timeout - Timeout to fail request
*
* @returns - Promise resolving the request
*/
export default function timeoutFetch(url, options, timeout = 500) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
]);
}
41 changes: 41 additions & 0 deletions app/util/ipfs-gateways.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[
{ "value": "https://ipfs.io/ipfs/", "key": 0, "label": "https://ipfs.io/ipfs/" },
{ "value": "https://gateway.ipfs.io/ipfs/", "key": 1, "label": "https://gateway.ipfs.io/ipfs/" },
{ "value": "https://ipfs.infura.io/ipfs/", "key": 2, "label": "https://ipfs.infura.io/ipfs/" },
{ "value": "https://rx14.co.uk/ipfs/", "key": 3, "label": "https://rx14.co.uk/ipfs/" },
{ "value": "https://ninetailed.ninja/ipfs/", "key": 4, "label": "https://ninetailed.ninja/ipfs/" },
{ "value": "https://upload.global/ipfs/", "key": 5, "label": "https://upload.global/ipfs/" },
{ "value": "https://ipfs.jes.xxx/ipfs/", "key": 6, "label": "https://ipfs.jes.xxx/ipfs/" },
{ "value": "https://catalunya.network/ipfs/", "key": 7, "label": "https://catalunya.network/ipfs/" },
{ "value": "https://siderus.io/ipfs/", "key": 8, "label": "https://siderus.io/ipfs/" },
{ "value": "https://ipfs.eternum.io/ipfs/", "key": 9, "label": "https://ipfs.eternum.io/ipfs/" },
{ "value": "https://hardbin.com/ipfs/", "key": 10, "label": "https://hardbin.com/ipfs/" },
{ "value": "https://ipfs.macholibre.org/ipfs/", "key": 11, "label": "https://ipfs.macholibre.org/ipfs/" },
{ "value": "https://ipfs.works/ipfs/", "key": 12, "label": "https://ipfs.works/ipfs/" },
{ "value": "https://ipfs.wa.hle.rs/ipfs/", "key": 13, "label": "https://ipfs.wa.hle.rs/ipfs/" },
{ "value": "https://api.wisdom.sh/ipfs/", "key": 14, "label": "https://api.wisdom.sh/ipfs/" },
{ "value": "https://gateway.blocksec.com/ipfs/", "key": 15, "label": "https://gateway.blocksec.com/ipfs/" },
{ "value": "https://ipfs.renehsz.com/ipfs/", "key": 16, "label": "https://ipfs.renehsz.com/ipfs/" },
{ "value": "https://cloudflare-ipfs.com/ipfs/", "key": 17, "label": "https://cloudflare-ipfs.com/ipfs/" },
{ "value": "https://ipns.co/", "key": 18, "label": "https://ipns.co/" },
{ "value": "https://ipfs.netw0rk.io/ipfs/", "key": 19, "label": "https://ipfs.netw0rk.io/ipfs/" },
{ "value": "https://gateway.swedneck.xyz/ipfs/", "key": 20, "label": "https://gateway.swedneck.xyz/ipfs/" },
{ "value": "https://ipfs.mrh.io/ipfs/", "key": 21, "label": "https://ipfs.mrh.io/ipfs/" },
{
"value": "https://gateway.originprotocol.com/ipfs/",
"key": 22,
"label": "https://gateway.originprotocol.com/ipfs/"
},
{ "value": "https://ipfs.dapps.earth/ipfs/", "key": 23, "label": "https://ipfs.dapps.earth/ipfs/" },
{ "value": "https://gateway.pinata.cloud/ipfs/", "key": 24, "label": "https://gateway.pinata.cloud/ipfs/" },
{ "value": "https://ipfs.doolta.com/ipfs/", "key": 25, "label": "https://ipfs.doolta.com/ipfs/" },
{ "value": "https://ipfs.sloppyta.co/ipfs/", "key": 26, "label": "https://ipfs.sloppyta.co/ipfs/" },
{ "value": "https://ipfs.busy.org/ipfs/", "key": 27, "label": "https://ipfs.busy.org/ipfs/" },
{ "value": "https://ipfs.greyh.at/ipfs/", "key": 28, "label": "https://ipfs.greyh.at/ipfs/" },
{ "value": "https://gateway.serph.network/ipfs/", "key": 29, "label": "https://gateway.serph.network/ipfs/" },
{ "value": "https://jorropo.ovh/ipfs/", "key": 30, "label": "https://jorropo.ovh/ipfs/" },
{ "value": "https://ipfs.deo.moe/ipfs/", "key": 31, "label": "https://ipfs.deo.moe/ipfs/" },
{ "value": "https://gateway.temporal.cloud/ipfs/", "key": 32, "label": "https://gateway.temporal.cloud/ipfs/" },
{ "value": "https://ipfs.fooock.com/ipfs/", "key": 33, "label": "https://ipfs.fooock.com/ipfs/" },
{ "value": "https://cdn.cwinfo.net/ipfs/", "key": 34, "label": "https://cdn.cwinfo.net/ipfs/" }
]
5 changes: 4 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
"title": "Settings",
"current_conversion": "Base Currency",
"current_language": "Current Language",
"ipfs_gateway": "IPFS Gateway",
"ipfs_gateway_down": "Your current IPFS gateway is down",
"ipfs_gateway_desc": "Choose your preferred IPFS gateway.",
"search_engine": "Search Engine",
"new_RPC_URL": "New RPC Network",
"state_logs": "State Logs",
Expand Down Expand Up @@ -225,7 +228,7 @@
"general_title": "General",
"general_desc": "Currency conversion, primary currency, language",
"advanced_title": "Advanced",
"advanced_desc": "Access developer features, reset account, setup testnets, sync with extension, state logs and custom RPC",
"advanced_desc": "Access developer features, reset account, setup testnets, sync with extension, state logs, IPFS gateway and custom RPC",
"security_title": "Security & Privacy",
"security_desc": "Privacy settings, private key and wallet seed phrase",
"info_title": "About MetaMask",
Expand Down
Loading

0 comments on commit afaac75

Please sign in to comment.