1
0
mirror of https://github.com/oceanprotocol/commons.git synced 2023-03-15 18:03:00 +01:00

add default env vars into CONNECTIONS cofig

This commit is contained in:
Max Berman 2020-01-15 12:13:17 +01:00
parent b4fc9883e2
commit ab342b3fa6
9 changed files with 503 additions and 462 deletions

View File

@ -9,28 +9,28 @@ import './styles/global.scss'
import styles from './App.module.scss'
export default class App extends Component {
public render() {
return (
<div className={styles.app}>
<Router>
<>
<Header />
public render() {
return (
<div className={styles.app}>
<Router>
<>
<Header />
<main className={styles.main}>
{this.context.isLoading ? (
<div className={styles.loader}>
<Spinner message={this.context.message} />
</div>
) : (
<Routes />
)}
</main>
<Footer />
</>
</Router>
</div>
)
}
<main className={styles.main}>
{this.context.isLoading ? (
<div className={styles.loader}>
<Spinner message={this.context.message} />
</div>
) : (
<Routes />
)}
</main>
<Footer />
</>
</Router>
</div>
)
}
}
App.contextType = User

View File

@ -8,34 +8,34 @@ import menu from '../../data/menu'
import meta from '../../data/meta.json'
const MenuItem = ({ item }: { item: any }) => (
<NavLink
to={item.link}
className={styles.link}
activeClassName={styles.linkActive}
exact
>
{item.title}
</NavLink>
<NavLink
to={item.link}
className={styles.link}
activeClassName={styles.linkActive}
exact
>
{item.title}
</NavLink>
)
export default class Header extends PureComponent {
public render() {
return (
<header className={styles.header}>
<div className={styles.headerContent}>
<NavLink to="/" className={styles.headerLogo}>
<Logo className={styles.headerLogoImage} />
<h1 className={styles.headerTitle}>{meta.title}</h1>
</NavLink>
public render() {
return (
<header className={styles.header}>
<div className={styles.headerContent}>
<NavLink to="/" className={styles.headerLogo}>
<Logo className={styles.headerLogoImage} />
<h1 className={styles.headerTitle}>{meta.title}</h1>
</NavLink>
<nav className={styles.headerMenu}>
{menu.map(item => (
<MenuItem key={item.title} item={item} />
))}
<AccountStatus className={styles.accountStatus} />
</nav>
</div>
</header>
)
}
<nav className={styles.headerMenu}>
{menu.map(item => (
<MenuItem key={item.title} item={item} />
))}
<AccountStatus className={styles.accountStatus} />
</nav>
</div>
</header>
)
}
}

View File

@ -9,151 +9,153 @@ import ReactGA from 'react-ga'
import cleanupContentType from '../../../utils/cleanupContentType'
export const messages: any = {
99: 'Decrypting file URL...',
0: '1/3<br />Asking for agreement signature...',
1: '1/3<br />Agreement initialized.',
2: '2/3<br />Asking for two payment confirmations...',
3: '2/3<br />Payment confirmed. Requesting access...',
4: '3/3<br /> Access granted. Consuming file...'
99: 'Decrypting file URL...',
0: '1/3<br />Asking for agreement signature...',
1: '1/3<br />Agreement initialized.',
2: '2/3<br />Asking for two payment confirmations...',
3: '2/3<br />Payment confirmed. Requesting access...',
4: '3/3<br /> Access granted. Consuming file...'
}
interface AssetFileProps {
file: File
ddo: DDO
file: File
ddo: DDO
}
interface AssetFileState {
isLoading: boolean
error: string
step: number
isLoading: boolean
error: string
step: number
}
export default class AssetFile extends PureComponent<
AssetFileProps,
AssetFileState
AssetFileProps,
AssetFileState
> {
public static contextType = User
public static contextType = User
public state = {
isLoading: false,
error: '',
step: 99
}
private resetState = () =>
this.setState({
isLoading: true,
error: '',
step: 99
})
private purchaseAsset = async (ddo: DDO, index: number) => {
this.resetState()
ReactGA.event({
category: 'Purchase',
action: 'purchaseAsset-start ' + ddo.id
})
const { ocean } = this.context
try {
const accounts = await ocean.accounts.list()
const service = ddo.findServiceByType('access')
const agreements = await ocean.keeper.conditions.accessSecretStoreCondition.getGrantedDidByConsumer(
accounts[0].id
)
const agreement = agreements.find((element: any) => {
return element.did === ddo.id
})
let agreementId
if (agreement) {
;({ agreementId } = agreement)
} else {
agreementId = await ocean.assets
.order(ddo.id, service.index, accounts[0])
.next((step: number) => this.setState({ step }))
}
// manually add another step here for better UX
this.setState({ step: 4 })
const path = await ocean.assets.consume(
agreementId,
ddo.id,
service.index,
accounts[0],
'',
index
)
Logger.log('path', path)
ReactGA.event({
category: 'Purchase',
action: 'purchaseAsset-end ' + ddo.id
})
this.setState({ isLoading: false })
} catch (error) {
Logger.error('error', error.message)
this.setState({
public state = {
isLoading: false,
error: `${error.message}. Sorry about that, can you try again?`
})
ReactGA.event({
category: 'Purchase',
action: 'purchaseAsset-error ' + error.message
})
error: '',
step: 99
}
}
public render() {
const { ddo, file } = this.props
const { isLoading, error, step } = this.state
const { isLogged } = this.context
const { index, contentType, contentLength } = file
private resetState = () =>
this.setState({
isLoading: true,
error: '',
step: 99
})
return (
<div className={styles.fileWrap}>
<ul key={index} className={styles.file}>
{contentType || contentLength ? (
<>
<li>{cleanupContentType(contentType)}</li>
<li>
{contentLength && contentLength !== '0'
? filesize(contentLength)
: ''}
</li>
{/* <li>{encoding}</li> */}
{/* <li>{compression}</li> */}
</>
) : (
<li className={styles.empty}>No file info available</li>
)}
</ul>
private purchaseAsset = async (ddo: DDO, index: number) => {
this.resetState()
{isLoading ? (
<Spinner message={messages[step]} />
) : (
<Market.Consumer>
{market => (
<Button
primary
className={styles.buttonMain}
// weird 0 hack so TypeScript is happy
onClick={() => this.purchaseAsset(ddo, index || 0)}
disabled={!isLogged || !market.networkMatch}
name="Download"
>
Get file
</Button>
)}
</Market.Consumer>
)}
ReactGA.event({
category: 'Purchase',
action: 'purchaseAsset-start ' + ddo.id
})
{error !== '' && <div className={styles.error}>{error}</div>}
</div>
)
}
const { ocean } = this.context
try {
const accounts = await ocean.accounts.list()
const service = ddo.findServiceByType('access')
const agreements = await ocean.keeper.conditions.accessSecretStoreCondition.getGrantedDidByConsumer(
accounts[0].id
)
const agreement = agreements.find((element: any) => {
return element.did === ddo.id
})
let agreementId
if (agreement) {
;({ agreementId } = agreement)
} else {
agreementId = await ocean.assets
.order(ddo.id, service.index, accounts[0])
.next((step: number) => this.setState({ step }))
}
// manually add another step here for better UX
this.setState({ step: 4 })
const path = await ocean.assets.consume(
agreementId,
ddo.id,
service.index,
accounts[0],
'',
index
)
Logger.log('path', path)
ReactGA.event({
category: 'Purchase',
action: 'purchaseAsset-end ' + ddo.id
})
this.setState({ isLoading: false })
} catch (error) {
Logger.error('error', error.message)
this.setState({
isLoading: false,
error: `${error.message}. Sorry about that, can you try again?`
})
ReactGA.event({
category: 'Purchase',
action: 'purchaseAsset-error ' + error.message
})
}
}
public render() {
const { ddo, file } = this.props
const { isLoading, error, step } = this.state
const { isLogged } = this.context
const { index, contentType, contentLength } = file
return (
<div className={styles.fileWrap}>
<ul key={index} className={styles.file}>
{contentType || contentLength ? (
<>
<li>{cleanupContentType(contentType)}</li>
<li>
{contentLength && contentLength !== '0'
? filesize(contentLength)
: ''}
</li>
{/* <li>{encoding}</li> */}
{/* <li>{compression}</li> */}
</>
) : (
<li className={styles.empty}>No file info available</li>
)}
</ul>
{isLoading ? (
<Spinner message={messages[step]} />
) : (
<Market.Consumer>
{market => (
<Button
primary
className={styles.buttonMain}
// weird 0 hack so TypeScript is happy
onClick={() =>
this.purchaseAsset(ddo, index || 0)
}
disabled={!isLogged || !market.networkMatch}
name="Download"
>
Get file
</Button>
)}
</Market.Consumer>
)}
{error !== '' && <div className={styles.error}>{error}</div>}
</div>
)
}
}

View File

@ -27,27 +27,60 @@ export const ipfsNodeUri =
//
export const CONNECTIONS = {
pacific: {
nodeUri: 'https://pacific.oceanprotocol.com',
aquariusUri: 'https://aquarius.commons.oceanprotocol.com',
brizoUri: 'https://brizo.commons.oceanprotocol.com',
brizoAddress: '0x008c25ed3594e094db4592f4115d5fa74c4f41ea',
secretStoreUri: 'https://secret-store.oceanprotocol.com',
faucetUri: 'https://faucet.oceanprotocol.com'
nodeUri:
process.env.REACT_APP_NODE_URI ||
'https://pacific.oceanprotocol.com',
aquariusUri:
process.env.REACT_APP_AQUARIUS_URI ||
'https://aquarius.commons.oceanprotocol.com',
brizoUri:
process.env.REACT_APP_BRIZO_URI ||
'https://brizo.commons.oceanprotocol.com',
brizoAddress:
process.env.REACT_APP_BRIZO_ADDRESS ||
'0x008c25ed3594e094db4592f4115d5fa74c4f41ea',
secretStoreUri:
process.env.REACT_APP_SECRET_STORE_URI ||
'https://secret-store.oceanprotocol.com',
faucetUri:
process.env.REACT_APP_FAUCET_URI ||
'https://faucet.oceanprotocol.com'
},
nile: {
nodeUri: 'https://nile.dev-ocean.com',
aquariusUri: 'https://aquarius.nile.dev-ocean.com',
brizoUri: 'https://brizo.nile.dev-ocean.com',
brizoAddress: '0x4aaab179035dc57b35e2ce066919048686f82972',
secretStoreUri: 'https://secret-store.nile.dev-ocean.com',
faucetUri: 'https://faucet.nile.dev-ocean.com'
nodeUri: process.env.REACT_APP_NODE_URI || 'https://nile.dev-ocean.com',
aquariusUri:
process.env.REACT_APP_AQUARIUS_URI ||
'https://aquarius.nile.dev-ocean.com',
brizoUri:
process.env.REACT_APP_BRIZO_URI ||
'https://brizo.nile.dev-ocean.com',
brizoAddress:
process.env.REACT_APP_BRIZO_ADDRESS ||
'0x4aaab179035dc57b35e2ce066919048686f82972',
secretStoreUri:
process.env.REACT_APP_SECRET_STORE_URI ||
'https://secret-store.nile.dev-ocean.com',
faucetUri:
process.env.REACT_APP_FAUCET_URI ||
'https://faucet.nile.dev-ocean.com'
},
duero: {
nodeUri: 'https://duero.dev-ocean.com',
aquariusUri: 'https://aquarius.duero.dev-ocean.com',
brizoUri: 'https://brizo.duero.dev-ocean.com',
brizoAddress: '0x9d4ed58293f71122ad6a733c1603927a150735d0',
secretStoreUri: 'https://secret-store.duero.dev-ocean.com',
faucetUri: 'https://faucet.duero.dev-ocean.com'
nodeUri:
process.env.REACT_APP_NODE_URI || 'https://duero.dev-ocean.com',
aquariusUri:
process.env.REACT_APP_AQUARIUS_URI ||
'https://aquarius.duero.dev-ocean.com',
brizoUri:
process.env.REACT_APP_BRIZO_URI ||
'https://brizo.duero.dev-ocean.com',
brizoAddress:
process.env.REACT_APP_BRIZO_ADDRESS ||
'0x9d4ed58293f71122ad6a733c1603927a150735d0',
secretStoreUri:
process.env.REACT_APP_SECRET_STORE_URI ||
'https://secret-store.duero.dev-ocean.com',
faucetUri:
process.env.REACT_APP_FAUCET_URI ||
'https://faucet.duero.dev-ocean.com'
}
}

View File

@ -1,48 +1,48 @@
import Web3 from 'web3'
export class MetamaskProvider {
private web3: Web3
private web3: Web3
public constructor() {
// Default
this.web3 = null as any
// Modern dapp browsers
if (window.ethereum) {
this.web3 = new Web3(window.ethereum)
public constructor() {
// Default
this.web3 = null as any
// Modern dapp browsers
if (window.ethereum) {
this.web3 = new Web3(window.ethereum)
}
// Legacy dapp browsers
else if (window.web3) {
this.web3 = new Web3(window.web3.currentProvider)
}
}
// Legacy dapp browsers
else if (window.web3) {
this.web3 = new Web3(window.web3.currentProvider)
public async isAvailable() {
return this.web3 !== null
}
}
public async isAvailable() {
return this.web3 !== null
}
public async isLogged() {
if (this.web3 === null) return false
if ((await this.web3.eth.getAccounts()).length > 0) {
return true
public async isLogged() {
if (this.web3 === null) return false
if ((await this.web3.eth.getAccounts()).length > 0) {
return true
}
return false
}
return false
}
public async startLogin() {
try {
await window.ethereum.enable()
localStorage.setItem('logType', 'Metamask')
} catch (error) {
return false
public async startLogin() {
try {
await window.ethereum.enable()
localStorage.setItem('logType', 'Metamask')
} catch (error) {
return false
}
}
}
public async logout() {
localStorage.removeItem('logType')
// reload page?
}
public async logout() {
localStorage.removeItem('logType')
// reload page?
}
public getProvider() {
return this.web3
}
public getProvider() {
return this.web3
}
}

View File

@ -5,16 +5,16 @@ import App from './App'
import * as serviceWorker from './serviceWorker'
function renderToDOM() {
const root = document.getElementById('root')
const root = document.getElementById('root')
if (root !== null) {
ReactDOM.render(
<UserProvider>
<App />
</UserProvider>,
root
)
}
if (root !== null) {
ReactDOM.render(
<UserProvider>
<App />
</UserProvider>,
root
)
}
}
export { renderToDOM }

View File

@ -1,22 +1,22 @@
import { MetaData } from '@oceanprotocol/squid'
const AssetModel: MetaData = {
// OEP-08 Attributes
// https://github.com/oceanprotocol/OEPs/tree/master/8
main: {
type: 'dataset',
name: '',
dateCreated: '',
author: '',
license: '',
price: '',
files: []
},
additionalInformation: {
description: '',
copyrightHolder: '',
categories: []
}
// OEP-08 Attributes
// https://github.com/oceanprotocol/OEPs/tree/master/8
main: {
type: 'dataset',
name: '',
dateCreated: '',
author: '',
license: '',
price: '',
files: []
},
additionalInformation: {
description: '',
copyrightHolder: '',
categories: []
}
}
export default AssetModel

View File

@ -10,48 +10,48 @@ const history = createMemoryHistory()
const location = createLocation('/faucet')
const setup = () => {
const utils = render(
<User.Provider value={userMockConnected}>
<Market.Provider
value={{
network: 'pacific',
totalAssets: 100,
categories: [''],
networkMatch: true
}}
>
<MemoryRouter>
<Faucet
history={history}
location={location}
match={{ params: '', path: '', url: '', isExact: true }}
/>
</MemoryRouter>
</Market.Provider>
</User.Provider>
)
const button = utils.getByText('Request ETH')
const { container } = utils
return { button, container, ...utils }
const utils = render(
<User.Provider value={userMockConnected}>
<Market.Provider
value={{
network: 'pacific',
totalAssets: 100,
categories: [''],
networkMatch: true
}}
>
<MemoryRouter>
<Faucet
history={history}
location={location}
match={{ params: '', path: '', url: '', isExact: true }}
/>
</MemoryRouter>
</Market.Provider>
</User.Provider>
)
const button = utils.getByText('Request ETH')
const { container } = utils
return { button, container, ...utils }
}
describe('Faucet', () => {
it('renders without crashing', () => {
const { container } = setup()
expect(container.firstChild).toBeInTheDocument()
})
it('shows actions when connected', () => {
const { button } = setup()
expect(button).toBeInTheDocument()
expect(button).not.toHaveAttribute('disabled')
})
it('fires requestFromFaucet', async () => {
await act(async () => {
const { button } = setup()
fireEvent.click(button)
it('renders without crashing', () => {
const { container } = setup()
expect(container.firstChild).toBeInTheDocument()
})
it('shows actions when connected', () => {
const { button } = setup()
expect(button).toBeInTheDocument()
expect(button).not.toHaveAttribute('disabled')
})
it('fires requestFromFaucet', async () => {
await act(async () => {
const { button } = setup()
fireEvent.click(button)
})
expect(userMockConnected.requestFromFaucet).toHaveBeenCalledTimes(1)
})
expect(userMockConnected.requestFromFaucet).toHaveBeenCalledTimes(1)
})
})

View File

@ -1,9 +1,9 @@
import React, {
lazy,
Suspense,
FormEvent,
PureComponent,
ChangeEvent
lazy,
Suspense,
FormEvent,
PureComponent,
ChangeEvent
} from 'react'
import axios from 'axios'
import { Logger, File } from '@oceanprotocol/squid'
@ -21,196 +21,202 @@ import Spinner from '../../../components/atoms/Spinner'
const Ipfs = lazy(() => import('./Ipfs'))
export interface FilePublish extends File {
found: boolean // non-standard
found: boolean // non-standard
}
interface FilesProps {
files: File[]
placeholder: string
help?: string
name: string
onChange(
event:
| ChangeEvent<HTMLInputElement>
| FormEvent<HTMLInputElement>
| ChangeEvent<HTMLSelectElement>
| ChangeEvent<HTMLTextAreaElement>
): void
files: File[]
placeholder: string
help?: string
name: string
onChange(
event:
| ChangeEvent<HTMLInputElement>
| FormEvent<HTMLInputElement>
| ChangeEvent<HTMLSelectElement>
| ChangeEvent<HTMLTextAreaElement>
): void
}
interface FilesStates {
isFormShown: boolean
isIpfsFormShown: boolean
isFormShown: boolean
isIpfsFormShown: boolean
}
const buttons = [
{
id: 'url',
title: '+ From URL',
titleActive: '- Cancel'
},
{
id: 'ipfs',
title: '+ Add to IPFS',
titleActive: '- Cancel'
}
{
id: 'url',
title: '+ From URL',
titleActive: '- Cancel'
},
{
id: 'ipfs',
title: '+ Add to IPFS',
titleActive: '- Cancel'
}
]
export default class Files extends PureComponent<FilesProps, FilesStates> {
public state: FilesStates = {
isFormShown: false,
isIpfsFormShown: false
}
// for canceling axios requests
public signal = axios.CancelToken.source()
public componentWillUnmount() {
this.signal.cancel()
}
private toggleForm = (e: Event, form: string) => {
e.preventDefault()
this.setState({
isFormShown: form === 'url' ? !this.state.isFormShown : false,
isIpfsFormShown: form === 'ipfs' ? !this.state.isIpfsFormShown : false
})
}
private async getFile(url: string) {
const file: FilePublish = {
url,
contentType: '',
found: false // non-standard
}
try {
const response = await axios({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
url: `${serviceUri}/api/v1/urlcheck`,
data: { url },
cancelToken: this.signal.token
})
const { contentLength, contentType, found } = response.data.result
if (contentLength) file.contentLength = contentLength
if (contentType) {
file.contentType = contentType
file.compression = cleanupContentType(contentType)
}
file.found = found
return file
} catch (error) {
!axios.isCancel(error) && Logger.error(error.message)
}
}
private addFile = async (url: string) => {
// check for duplicate urls
const duplicateFiles = this.props.files.filter(props =>
url.includes(props.url)
)
if (duplicateFiles.length > 0) {
return this.setState({
public state: FilesStates = {
isFormShown: false,
isIpfsFormShown: false
})
}
const file: FilePublish | undefined = await this.getFile(url)
file && this.props.files.push(file)
// for canceling axios requests
public signal = axios.CancelToken.source()
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
public componentWillUnmount() {
this.signal.cancel()
}
this.props.onChange(event as any)
this.setState({
isFormShown: false,
isIpfsFormShown: false
})
private toggleForm = (e: Event, form: string) => {
e.preventDefault()
this.forceUpdate()
}
private removeFile = (index: number) => {
this.props.files.splice(index, 1)
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
this.setState({
isFormShown: form === 'url' ? !this.state.isFormShown : false,
isIpfsFormShown:
form === 'ipfs' ? !this.state.isIpfsFormShown : false
})
}
this.props.onChange(event as any)
this.forceUpdate()
}
public render() {
const { files, help, placeholder, name, onChange } = this.props
const { isFormShown, isIpfsFormShown } = this.state
private async getFile(url: string) {
const file: FilePublish = {
url,
contentType: '',
found: false // non-standard
}
return (
<>
{help && <Help>{help}</Help>}
try {
const response = await axios({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
url: `${serviceUri}/api/v1/urlcheck`,
data: { url },
cancelToken: this.signal.token
})
{/* Use hidden input to collect files */}
<input
type="hidden"
name={name}
value={JSON.stringify(files)}
onChange={onChange}
data-testid="files"
/>
const { contentLength, contentType, found } = response.data.result
<div className={styles.newItems}>
{files.length > 0 && (
<ul className={styles.itemsList}>
{files.map((item: any, index: number) => (
<Item
key={shortid.generate()}
item={item}
removeFile={() => this.removeFile(index)}
if (contentLength) file.contentLength = contentLength
if (contentType) {
file.contentType = contentType
file.compression = cleanupContentType(contentType)
}
file.found = found
return file
} catch (error) {
!axios.isCancel(error) && Logger.error(error.message)
}
}
private addFile = async (url: string) => {
// check for duplicate urls
const duplicateFiles = this.props.files.filter(props =>
url.includes(props.url)
)
if (duplicateFiles.length > 0) {
return this.setState({
isFormShown: false,
isIpfsFormShown: false
})
}
const file: FilePublish | undefined = await this.getFile(url)
file && this.props.files.push(file)
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
}
this.props.onChange(event as any)
this.setState({
isFormShown: false,
isIpfsFormShown: false
})
this.forceUpdate()
}
private removeFile = (index: number) => {
this.props.files.splice(index, 1)
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
}
this.props.onChange(event as any)
this.forceUpdate()
}
public render() {
const { files, help, placeholder, name, onChange } = this.props
const { isFormShown, isIpfsFormShown } = this.state
return (
<>
{help && <Help>{help}</Help>}
{/* Use hidden input to collect files */}
<input
type="hidden"
name={name}
value={JSON.stringify(files)}
onChange={onChange}
data-testid="files"
/>
))}
</ul>
)}
{buttons.map(button => {
const isActive =
(button.id === 'url' && isFormShown) ||
(button.id === 'ipfs' && isIpfsFormShown)
<div className={styles.newItems}>
{files.length > 0 && (
<ul className={styles.itemsList}>
{files.map((item: any, index: number) => (
<Item
key={shortid.generate()}
item={item}
removeFile={() => this.removeFile(index)}
/>
))}
</ul>
)}
return (
<Button
key={shortid.generate()}
link
onClick={(e: Event) => this.toggleForm(e, button.id)}
>
{isActive ? button.titleActive : button.title}
</Button>
)
})}
{buttons.map(button => {
const isActive =
(button.id === 'url' && isFormShown) ||
(button.id === 'ipfs' && isIpfsFormShown)
{isFormShown && (
<ItemForm placeholder={placeholder} addFile={this.addFile} />
)}
return (
<Button
key={shortid.generate()}
link
onClick={(e: Event) =>
this.toggleForm(e, button.id)
}
>
{isActive ? button.titleActive : button.title}
</Button>
)
})}
{isIpfsFormShown && (
<Suspense fallback={<Spinner message="Loading..." />}>
<Ipfs addFile={this.addFile} />
</Suspense>
)}
</div>
</>
)
}
{isFormShown && (
<ItemForm
placeholder={placeholder}
addFile={this.addFile}
/>
)}
{isIpfsFormShown && (
<Suspense fallback={<Spinner message="Loading..." />}>
<Ipfs addFile={this.addFile} />
</Suspense>
)}
</div>
</>
)
}
}