Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improved support for remote API #1613

Merged
merged 7 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
967 changes: 659 additions & 308 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"test": "run-s -cl test:unit test:build test:e2e",
"test:unit": "react-scripts test --env=jsdom --runInBand --watchAll=false",
"test:unit:watch": "react-scripts test --env=jsdom",
"test:e2e": "cross-env JEST_PUPPETEER_CONFIG=test/e2e/jest-puppeteer.config.js jest -c test/e2e/jest.config.js --runInBand",
"test:e2e": "cross-env JEST_PUPPETEER_CONFIG=test/e2e/jest-puppeteer.config.js jest --verbose -c test/e2e/jest.config.js --runInBand",
"test:build": "shx test -f build/index.html || run-s build",
"test:coverage": "react-scripts test --coverage",
"analyze": "webpack-bundle-analyzer build/stats.json",
Expand Down Expand Up @@ -48,7 +48,7 @@
"ipfs-css": "^1.2.0",
"ipfs-geoip": "^5.0.1",
"ipfs-http-client": "^46.0.0",
"ipfs-provider": "^1.0.0",
"ipfs-provider": "^1.1.0",
"ipld-explorer-components": "1.6.0",
"is-binary": "^0.1.0",
"is-ipfs": "^1.0.3",
Expand Down Expand Up @@ -79,7 +79,7 @@
"react-loadable": "^5.5.0",
"react-overlays": "^2.1.1",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.0",
"react-scripts": "^3.4.3",
"react-spring": "^8.0.27",
"react-test-renderer": "^16.13.1",
"react-virtualized": "^9.21.2",
Expand All @@ -103,6 +103,7 @@
"@svgr/cli": "^5.4.0",
"@types/node": "^14.0.27",
"babel-eslint": "^10.1.0",
"basic-auth": "^2.0.1",
"big.js": "^5.2.2",
"bundlesize": "^0.18.0",
"cross-env": "^6.0.3",
Expand All @@ -116,14 +117,15 @@
"fake-indexeddb": "^3.1.2",
"get-port": "^5.1.1",
"go-ipfs": "0.6.0",
"http-proxy": "^1.18.1",
"http-server": "^0.12.3",
"ipfs": "^0.48.1",
"ipfsd-ctl": "^5.0.0",
"ipfsd-ctl": "^7.0.0",
"is-pull-stream": "0.0.0",
"jest-puppeteer": "^4.4.0",
"multihashing-async": "^1.0.0",
"npm-run-all": "^4.1.5",
"puppeteer": "^3.3.0",
"puppeteer": "^5.2.1",
"run-script-os": "^1.1.1",
"shx": "^0.3.2",
"typescript": "3.9.7",
Expand Down
1 change: 1 addition & 0 deletions public/locales/en/status.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"paragraph1": "Hosting <0>{repoSize} of files</0>",
"paragraph2": "{count, plural, one {Discovered <0>1 peer</0>} other {Discovered <0>{peersCount} peers</0>}}"
},
"customApiConfig": "Custom JSON configuration",
"AskToEnable": {
"label": "Help improve this app by sending anonymous usage data."
},
Expand Down
6 changes: 4 additions & 2 deletions src/bundles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ function getURLFromAddress (name, config) {
const address = Array.isArray(config.Addresses[name])
? config.Addresses[name][0]
: config.Addresses[name]
return toUri(address).replace('tcp://', 'http://')
const url = toUri(address, { assumeHttp: true })
if (new URL(url).port === 0) throw Error('port set to 0, not deterministic')
return url
} catch (error) {
console.log(`Failed to get url from Addresses.${name}`, error)
console.log(`Failed to get url from config at Addresses.${name}`, error)
return null
}
}
Expand Down
58 changes: 49 additions & 9 deletions src/bundles/ipfs-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const update = (state, message) => {
ready: true,
failed: false,
provider: message.payload.provider,
apiAddress: message.payload.apiAddress || state.apiAddress
apiAddress: asAPIOptions(message.payload.apiAddress || state.apiAddress)
}
}
case 'IPFS_STOPPED': {
Expand Down Expand Up @@ -101,14 +101,17 @@ const init = () => {
*/
const readAPIAddressSetting = () => {
const setting = readSetting('ipfsApi')
return setting == null ? null : asAPIAddress(setting)
return setting == null ? null : asAPIOptions(setting)
}

const asAPIAddress = (value) => asMultiaddress(value) || asURL(value)
/**
* @returns {object|string|null}
*/
const asAPIOptions = (value) => asHttpClientOptions(value) || asMultiaddress(value) || asURL(value)

/**
* Attempts to turn cast given value into `URL` instance. Return either `URL`
* instance or `null`.
* Attempts to turn cast given value into URL.
* Return either string instance or `null`.
* @param {any} value
* @returns {string|null}
*/
Expand All @@ -121,16 +124,53 @@ const asURL = (value) => {
}

/**
* Attempts to turn cast given value into `URL` instance. Return either `URL`
* instance or `null`.
* Attempts to turn cast given value into Multiaddr.
* Return either string instance or `null`.
* @param {any} value
* @returns {string|null}
*/
const asMultiaddress = (value) => {
if (value != null) {
// ignore empty string, as it will produce '/'
if (value != null && value !== '') {
try {
return multiaddr(value).toString()
} catch (_) {}
}
return null
}

/**
* Attempts to turn cast given value into options object compatible with ipfs-http-client constructor.
* Return either string with JSON or `null`.
* @param {any} value
* @returns {object|null}
*/
const asHttpClientOptions = (value) => {
try {
value = JSON.parse(value)
} catch (_) {}

// turn URL with inlined basic auth into client options object
try {
const uri = new URL(value)
const { username, password } = uri
if (username && password) {
value = {
host: uri.hostname,
port: uri.port || (uri.protocol === 'https:' ? '443' : '80'),
protocol: uri.protocol.split(':').shift(),
apiPath: (uri.pathname !== '/' ? uri.pathname : 'api/v0'),
headers: {
authorization: `Basic ${btoa(username + ':' + password)}`
}
}
}
} catch (_) { }

// https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client#importing-the-module-and-usage
if (value && (value.host || value.apiPath || value.protocol || value.port || value.headers)) {
return value
}
return null
}

Expand Down Expand Up @@ -219,7 +259,7 @@ const bundle = {
},

doUpdateIpfsApiAddress: (address) => async (store) => {
const apiAddress = asAPIAddress(address)
const apiAddress = asAPIOptions(address)
if (apiAddress == null) {
store.dispatch({ type: 'IPFS_API_ADDRESS_INVALID' })
} else {
Expand Down
21 changes: 16 additions & 5 deletions src/components/api-address-form/ApiAddressForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { connect } from 'redux-bundler-react'
import { withTranslation } from 'react-i18next'
import Button from '../button/Button'

const ApiAddressForm = ({ t, doUpdateIpfsApiAddress, ipfsApiAddress = '' }) => {
const [value, setValue] = useState(ipfsApiAddress)
const ApiAddressForm = ({ t, doUpdateIpfsApiAddress, ipfsApiAddress }) => {
const [value, setValue] = useState(asAPIString(ipfsApiAddress))

const onChange = (event) => setValue(event.target.value)

Expand All @@ -21,20 +21,31 @@ const ApiAddressForm = ({ t, doUpdateIpfsApiAddress, ipfsApiAddress = '' }) => {

return (
<form onSubmit={onSubmit}>
<input id='api-address'
<input
id='api-address'
aria-label={t('apiAddressForm.apiLabel')}
type='text'
className='w-100 lh-copy monospace f5 pl1 pv1 mb2 charcoal input-reset ba b--black-20 br1 focus-outline'
onChange={onChange}
onKeyPress={onKeyPress}
value={value} />
value={value}
/>
<div className='tr'>
<Button className="tc">{t('actions.submit')}</Button>
<Button className='tc'>{t('actions.submit')}</Button>
</div>
</form>
)
}

/**
* @returns {string}
*/
const asAPIString = (value) => {
if (value == null) return ''
if (typeof value === 'string') return value
return JSON.stringify(value)
}

export default connect(
'doUpdateIpfsApiAddress',
'selectIpfsApiAddress',
Expand Down
21 changes: 14 additions & 7 deletions src/status/NodeInfoAdvanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,26 @@ const NodeInfoAdvanced = ({ t, identity, ipfsProvider, ipfsApiAddress, gatewayUr
ev.preventDefault()
}

const asAPIString = (value) => {
// hide raw JSON if advanced config is present in the string
return typeof value !== 'string'
? t('customApiConfig')
: value
}

return (
<Details className='mt3 f6' summaryText={t('app:terms.advanced')} open={isNodeInfoOpen} onClick={handleSummaryClick}>
<DefinitionList className='mt3'>
<Definition advanced term={t('app:terms.gateway')} desc={gatewayUrl} />
{ipfsProvider === 'httpClient'
? <Definition advanced term={t('app:terms.api')} desc={
isMultiaddr(ipfsApiAddress)
? (
<div className="flex items-center">
<Address value={ipfsApiAddress} />
<a className='ml2 link blue sans-serif fw6' href="#/settings">{t('app:actions.edit')}</a>
</div>)
: ipfsApiAddress
(<div id="http-api-address" className="flex items-center">
{isMultiaddr(ipfsApiAddress)
? (<Address value={ipfsApiAddress} />)
: asAPIString(ipfsApiAddress)
}
<a className='ml2 link blue sans-serif fw6' href="#/settings">{t('app:actions.edit')}</a>
</div>)
} />
: <Definition advanced term={t('app:terms.api')} desc={<ProviderLink name={ipfsProvider} />} />
}
Expand Down
Loading