From adab473f4ede5280f477fb36a9fee430374ff265 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Mon, 15 Apr 2019 11:40:25 +0200 Subject: [PATCH] move web3/ocean detection out of App.jsx --- client/src/App.tsx | 265 ++---------------- .../molecules/AccountStatus/Indicator.tsx | 2 +- .../molecules/AccountStatus/Popover.tsx | 2 +- .../src/components/organisms/AssetsUser.tsx | 2 +- client/src/components/organisms/Header.tsx | 2 +- .../src/components/organisms/Web3message.tsx | 62 ++-- client/src/context/UserProvider.tsx | 254 +++++++++++++++++ client/src/context/{User.ts => index.tsx} | 3 +- client/src/data/web3message.json | 6 + client/src/routes/Details/AssetFile.tsx | 2 +- .../src/routes/Details/AssetFilesDetails.tsx | 2 +- client/src/routes/Details/index.tsx | 2 +- client/src/routes/Faucet.tsx | 2 +- client/src/routes/History.tsx | 2 +- client/src/routes/Publish/Step.tsx | 2 +- client/src/routes/Publish/index.tsx | 2 +- client/src/routes/Search.tsx | 2 +- 17 files changed, 309 insertions(+), 305 deletions(-) create mode 100644 client/src/context/UserProvider.tsx rename client/src/context/{User.ts => index.tsx} (93%) create mode 100644 client/src/data/web3message.json diff --git a/client/src/App.tsx b/client/src/App.tsx index 981b889..87d5ccd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,273 +1,44 @@ import React, { Component } from 'react' -import Web3 from 'web3' import { BrowserRouter as Router } from 'react-router-dom' -import { Logger } from '@oceanprotocol/squid' import Header from './components/organisms/Header' import Footer from './components/organisms/Footer' import Spinner from './components/atoms/Spinner' -import { User } from './context/User' -import { provideOcean } from './ocean' +import { User } from './context' +import UserProvider from './context/UserProvider' import Routes from './Routes' import './styles/global.scss' import styles from './App.module.scss' -import { - nodeHost, - nodePort, - nodeScheme, - faucetHost, - faucetPort, - faucetScheme -} from './config/config' - -const POLL_ACCOUNTS = 1000 // every 1s -const POLL_NETWORK = POLL_ACCOUNTS * 60 // every 1 min - -declare global { - interface Window { - web3: Web3 - ethereum: any - } -} - -interface AppState { - isLogged: boolean - isLoading: boolean - isWeb3: boolean - isNile: boolean - account: string - balance: { - eth: number - ocn: number - } - network: string - web3: Web3 - ocean: any - requestFromFaucet(): void - message: string -} - -class App extends Component<{}, AppState> { - private accountsInterval: any = null - private networkInterval: any = null - - private requestFromFaucet = async () => { - try { - const url = `${faucetScheme}://${faucetHost}:${faucetPort}/faucet` - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - address: this.state.account, - agent: 'commons' - }) - }) - return response.json() - } catch (error) { - Logger.log('requestFromFaucet', error) - } - } - - public state = { - isLogged: false, - isLoading: true, - isWeb3: false, - isNile: false, - balance: { - eth: 0, - ocn: 0 - }, - network: '', - web3: new Web3( - new Web3.providers.HttpProvider( - `${nodeScheme}://${nodeHost}:${nodePort}` - ) - ), - account: '', - ocean: {} as any, - requestFromFaucet: this.requestFromFaucet, - message: 'Connecting to Ocean...' - } - - public async componentDidMount() { - await this.bootstrap() - - this.initAccountsPoll() - this.initNetworkPoll() - } - - private bootstrap = async () => { - try { - // - // Start with Web3 detection - // - this.setState({ message: 'Setting up Web3...' }) - - // Modern dapp browsers - if (window.ethereum) { - window.web3 = new Web3(window.ethereum) - this.setState({ isWeb3: true }) - } - // Legacy dapp browsers - else if (window.web3) { - window.web3 = new Web3(window.web3.currentProvider) - this.setState({ isWeb3: true }) - } - // Non-dapp browsers - else { - this.setState({ isWeb3: false }) - } - - // Modern & legacy dapp browsers - if (this.state.isWeb3) { - // - // Detecting network with window.web3 - // - let isNile - - await window.web3.eth.net.getId((err, netId) => { - if (err) return - - isNile = netId === 8995 - const network = isNile ? 'Nile' : netId.toString() - - if ( - isNile !== this.state.isNile || - network !== this.state.network - ) { - this.setState({ isNile, network }) - } - }) - - if (!isNile) { - window.web3 = this.state.web3 - } - - // - // Provide the Ocean - // - this.setState({ message: 'Connecting to Ocean...' }) - - const { ocean } = await provideOcean(window.web3) - this.setState({ ocean, isLoading: false }) - - // Set proper network names now that we have Ocean - this.fetchNetwork() - - // Get accounts - this.fetchAccounts() - } - // Non-dapp browsers - else { - this.setState({ message: 'Connecting to Ocean...' }) - const { ocean } = await provideOcean(this.state.web3) - this.setState({ ocean, isLoading: false }) - - this.fetchNetwork() - } - } catch (e) { - // error in bootstrap process - // show error connecting to ocean - Logger.log('web3 error', e) - this.setState({ isLoading: false }) - } - } - - private initAccountsPoll() { - if (!this.accountsInterval) { - this.accountsInterval = setInterval( - this.fetchAccounts, - POLL_ACCOUNTS - ) - } - } - - private initNetworkPoll() { - if (!this.networkInterval) { - this.networkInterval = setInterval(this.fetchNetwork, POLL_NETWORK) - } - } - - private fetchAccounts = async () => { - const { ocean, isWeb3, isLogged, isNile } = this.state - - if (isWeb3) { - // Modern dapp browsers - if (window.ethereum) { - if (!isLogged && isNile) { - try { - await window.ethereum.enable() - } catch (error) { - // User denied account access... - this.accountsInterval = null - return - } - } - } - - const accounts = await ocean.accounts.list() - - if (accounts.length > 0) { - const account = accounts[0].getId() - - if (account !== this.state.account) { - this.setState({ account, isLogged: true }) - } - - const balance = await accounts[0].getBalance() - - if ( - balance.eth !== this.state.balance.eth || - balance.ocn !== this.state.balance.ocn - ) { - this.setState({ balance }) - } - } else { - isLogged !== false && - this.setState({ isLogged: false, account: '' }) - } - } - } - - private fetchNetwork = async () => { - const { ocean, isWeb3 } = this.state - - if (isWeb3) { - const network = await ocean.keeper.getNetworkName() - const isNile = network === 'Nile' - - network !== this.state.network && this.setState({ isNile, network }) - } - } - +export default class App extends Component { public render() { return (
- + <>
- {this.state.isLoading ? ( -
- -
- ) : ( - - )} + + {states => + states.isLoading ? ( +
+ +
+ ) : ( + + ) + } +
- +
) } } - -export default App diff --git a/client/src/components/molecules/AccountStatus/Indicator.tsx b/client/src/components/molecules/AccountStatus/Indicator.tsx index 5bfa917..dd7c88a 100644 --- a/client/src/components/molecules/AccountStatus/Indicator.tsx +++ b/client/src/components/molecules/AccountStatus/Indicator.tsx @@ -1,6 +1,6 @@ import React from 'react' import cx from 'classnames' -import { User } from '../../../context/User' +import { User } from '../../../context' import styles from './Indicator.module.scss' const Indicator = ({ diff --git a/client/src/components/molecules/AccountStatus/Popover.tsx b/client/src/components/molecules/AccountStatus/Popover.tsx index 82b4dbc..5ae2041 100644 --- a/client/src/components/molecules/AccountStatus/Popover.tsx +++ b/client/src/components/molecules/AccountStatus/Popover.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react' import Dotdotdot from 'react-dotdotdot' -import { User } from '../../../context/User' +import { User } from '../../../context' import styles from './Popover.module.scss' export default class Popover extends PureComponent<{ diff --git a/client/src/components/organisms/AssetsUser.tsx b/client/src/components/organisms/AssetsUser.tsx index d2058e8..e839262 100644 --- a/client/src/components/organisms/AssetsUser.tsx +++ b/client/src/components/organisms/AssetsUser.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react' import { Link } from 'react-router-dom' import { Logger } from '@oceanprotocol/squid' -import { User } from '../../context/User' +import { User } from '../../context' import Spinner from '../atoms/Spinner' import Asset from '../molecules/Asset' import styles from './AssetsUser.module.scss' diff --git a/client/src/components/organisms/Header.tsx b/client/src/components/organisms/Header.tsx index b3b0d9a..06d6935 100644 --- a/client/src/components/organisms/Header.tsx +++ b/client/src/components/organisms/Header.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react' import { NavLink } from 'react-router-dom' import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg' -import { User } from '../../context/User' +import { User } from '../../context' import AccountStatus from '../molecules/AccountStatus' import styles from './Header.module.scss' diff --git a/client/src/components/organisms/Web3message.tsx b/client/src/components/organisms/Web3message.tsx index 1bc3cb6..9765013 100644 --- a/client/src/components/organisms/Web3message.tsx +++ b/client/src/components/organisms/Web3message.tsx @@ -1,64 +1,36 @@ import React, { PureComponent } from 'react' import Dotdotdot from 'react-dotdotdot' -import Button from '../atoms/Button' import AccountStatus from '../molecules/AccountStatus' import styles from './Web3message.module.scss' -import { User } from '../../context/User' +import { User } from '../../context' +import content from '../../data/web3message.json' export default class Web3message extends PureComponent { - private noWeb3 = () => ( + private message = (message: string, account?: string) => (
- Not a Web3 Browser. For - publishing and downloading an asset you need to{' '} - - setup MetaMask - {' '} - or use any other Web3-capable plugin or browser. -
- ) - - private unlockAccount = () => ( -
- No accounts detected. - For publishing and downloading an asset you need to unlock your Web3 - account. -
- ) - - private haveAccount = (account: string) => ( -
- - - Connected with account - {account} - -
- ) - - private wrongNetwork = (network: string) => ( -
- Not connected to Nile - network, but to {network}.
- Please connect in MetaMask with Custom RPC{' '} - {`https://nile.dev-ocean.com`} + {' '} + {account ? ( + + {message} + {account} + + ) : ( + + )}
) public render() { - const { isWeb3, isNile, isLogged, network, account } = this.context + const { isWeb3, isNile, isLogged, account } = this.context return !isWeb3 - ? this.noWeb3() + ? this.message(content.noweb3) : !isNile - ? this.wrongNetwork(network) + ? this.message(content.wrongNetwork) : !isLogged - ? this.unlockAccount() + ? this.message(content.noAccount) : isLogged - ? this.haveAccount(account) + ? this.message(content.hasAccount, account) : null } } diff --git a/client/src/context/UserProvider.tsx b/client/src/context/UserProvider.tsx new file mode 100644 index 0000000..60a6507 --- /dev/null +++ b/client/src/context/UserProvider.tsx @@ -0,0 +1,254 @@ +import React, { Component } from 'react' +import Web3 from 'web3' +import { Logger } from '@oceanprotocol/squid' +import { User } from '.' +import { provideOcean } from '../ocean' + +import { + nodeHost, + nodePort, + nodeScheme, + faucetHost, + faucetPort, + faucetScheme +} from '../config/config' + +const POLL_ACCOUNTS = 1000 // every 1s +const POLL_NETWORK = POLL_ACCOUNTS * 60 // every 1 min + +declare global { + interface Window { + web3: Web3 + ethereum: { + enable(): void + host: string + supportsSubscriptions(): boolean + send(method: string, parameters: any[]): Promise + sendBatch(methods: any[], moduleInstance: any): Promise + } + } +} + +interface UserProviderState { + isLogged: boolean + isLoading: boolean + isWeb3: boolean + isNile: boolean + account: string + balance: { + eth: number + ocn: number + } + network: string + web3: Web3 + ocean: any + requestFromFaucet(): Promise<{}> + message: string +} + +export default class UserProvider extends Component<{}, UserProviderState> { + private accountsInterval: any = null + private networkInterval: any = null + + private requestFromFaucet = async () => { + try { + const url = `${faucetScheme}://${faucetHost}:${faucetPort}/faucet` + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + address: this.state.account, + agent: 'commons' + }) + }) + return response.json() + } catch (error) { + Logger.log('requestFromFaucet', error) + } + } + + public state = { + isLogged: false, + isLoading: true, + isWeb3: false, + isNile: false, + balance: { + eth: 0, + ocn: 0 + }, + network: '', + web3: new Web3( + new Web3.providers.HttpProvider( + `${nodeScheme}://${nodeHost}:${nodePort}` + ) + ), + account: '', + ocean: {} as any, + requestFromFaucet: this.requestFromFaucet, + message: 'Connecting to Ocean...' + } + + public async componentDidMount() { + await this.bootstrap() + + this.initAccountsPoll() + this.initNetworkPoll() + } + + private bootstrap = async () => { + try { + // + // Start with Web3 detection only + // + this.setState({ message: 'Setting up Web3...' }) + + // Modern dapp browsers + if (window.ethereum) { + window.web3 = new Web3(window.ethereum) + this.setState({ isWeb3: true }) + } + // Legacy dapp browsers + else if (window.web3) { + window.web3 = new Web3(window.web3.currentProvider) + this.setState({ isWeb3: true }) + } + // Non-dapp browsers + else { + this.setState({ isWeb3: false }) + } + + // Modern & legacy dapp browsers + if (this.state.isWeb3) { + // + // Detecting network with window.web3 + // + let isNile + + await window.web3.eth.net.getId((err, netId) => { + if (err) return + + isNile = netId === 8995 + const network = isNile ? 'Nile' : netId.toString() + + if ( + isNile !== this.state.isNile || + network !== this.state.network + ) { + this.setState({ isNile, network }) + } + }) + + if (!isNile) { + window.web3 = this.state.web3 + } + + // + // Provide the Ocean + // + this.setState({ message: 'Connecting to Ocean...' }) + + const { ocean } = await provideOcean(window.web3) + this.setState({ ocean }) + + // Set proper network names now that we have Ocean + await this.fetchNetwork() + + // Get accounts + await this.fetchAccounts() + + this.setState({ isLoading: false }) + } + // Non-dapp browsers + else { + this.setState({ message: 'Connecting to Ocean...' }) + const { ocean } = await provideOcean(this.state.web3) + this.setState({ ocean, isLoading: false }) + + this.fetchNetwork() + } + } catch (e) { + // error in bootstrap process + // show error connecting to ocean + Logger.log('web3 error', e) + this.setState({ isLoading: false }) + } + } + + private initAccountsPoll() { + if (!this.accountsInterval) { + this.accountsInterval = setInterval( + this.fetchAccounts, + POLL_ACCOUNTS + ) + } + } + + private initNetworkPoll() { + if (!this.networkInterval) { + this.networkInterval = setInterval(this.fetchNetwork, POLL_NETWORK) + } + } + + private fetchAccounts = async () => { + const { ocean, isWeb3, isLogged, isNile } = this.state + + if (isWeb3) { + // Modern dapp browsers + if (window.ethereum) { + if (!isLogged && isNile) { + try { + await window.ethereum.enable() + } catch (error) { + // User denied account access... + this.accountsInterval = null + return + } + } + } + + const accounts = await ocean.accounts.list() + + if (accounts.length > 0) { + const account = accounts[0].getId() + + if (account !== this.state.account) { + this.setState({ account, isLogged: true }) + } + + const balance = await accounts[0].getBalance() + + if ( + balance.eth !== this.state.balance.eth || + balance.ocn !== this.state.balance.ocn + ) { + this.setState({ balance }) + } + } else { + isLogged !== false && + this.setState({ isLogged: false, account: '' }) + } + } + } + + private fetchNetwork = async () => { + const { ocean, isWeb3 } = this.state + + if (isWeb3) { + const network = await ocean.keeper.getNetworkName() + const isNile = network === 'Nile' + + network !== this.state.network && this.setState({ isNile, network }) + } + } + + public render() { + return ( + + {this.props.children} + + ) + } +} diff --git a/client/src/context/User.ts b/client/src/context/index.tsx similarity index 93% rename from client/src/context/User.ts rename to client/src/context/index.tsx index d4d5f34..b9e8712 100644 --- a/client/src/context/User.ts +++ b/client/src/context/index.tsx @@ -15,5 +15,6 @@ export const User = React.createContext({ network: '', requestFromFaucet: () => { /* empty */ - } + }, + message: '' }) diff --git a/client/src/data/web3message.json b/client/src/data/web3message.json new file mode 100644 index 0000000..db9dc81 --- /dev/null +++ b/client/src/data/web3message.json @@ -0,0 +1,6 @@ +{ + "noweb3": "Not a Web3 Browser. For publishing and downloading an asset you need to setup MetaMask or use any other Web3-capable plugin or browser.", + "noAccount": "No accounts detected. For publishing and downloading an asset you need to unlock your Web3 account.", + "hasAccount": "Connected with account ", + "wrongNetwork": "Not connected to Nile network.
Please connect in MetaMask with Custom RPC https://nile.dev-ocean.com" +} diff --git a/client/src/routes/Details/AssetFile.tsx b/client/src/routes/Details/AssetFile.tsx index 698ddf7..32f7374 100644 --- a/client/src/routes/Details/AssetFile.tsx +++ b/client/src/routes/Details/AssetFile.tsx @@ -3,7 +3,7 @@ import { Logger } from '@oceanprotocol/squid' import filesize from 'filesize' import Button from '../../components/atoms/Button' import Spinner from '../../components/atoms/Spinner' -import { User } from '../../context/User' +import { User } from '../../context' import styles from './AssetFile.module.scss' import ReactGA from 'react-ga' diff --git a/client/src/routes/Details/AssetFilesDetails.tsx b/client/src/routes/Details/AssetFilesDetails.tsx index 9497920..4c5bdc7 100644 --- a/client/src/routes/Details/AssetFilesDetails.tsx +++ b/client/src/routes/Details/AssetFilesDetails.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react' import AssetFile from './AssetFile' -import { User } from '../../context/User' +import { User } from '../../context' import Web3message from '../../components/organisms/Web3message' import styles from './AssetFilesDetails.module.scss' diff --git a/client/src/routes/Details/index.tsx b/client/src/routes/Details/index.tsx index f54552f..b8131e0 100644 --- a/client/src/routes/Details/index.tsx +++ b/client/src/routes/Details/index.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react' import Route from '../../components/templates/Route' import Spinner from '../../components/atoms/Spinner' -import { User } from '../../context/User' +import { User } from '../../context' import AssetDetails from './AssetDetails' import stylesApp from '../../App.module.scss' diff --git a/client/src/routes/Faucet.tsx b/client/src/routes/Faucet.tsx index a4c7299..6590098 100644 --- a/client/src/routes/Faucet.tsx +++ b/client/src/routes/Faucet.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react' import Route from '../components/templates/Route' import Button from '../components/atoms/Button' import Spinner from '../components/atoms/Spinner' -import { User } from '../context/User' +import { User } from '../context' import Web3message from '../components/organisms/Web3message' import styles from './Faucet.module.scss' diff --git a/client/src/routes/History.tsx b/client/src/routes/History.tsx index 329f640..a895287 100644 --- a/client/src/routes/History.tsx +++ b/client/src/routes/History.tsx @@ -2,7 +2,7 @@ import React, { Component } from 'react' import Route from '../components/templates/Route' import AssetsUser from '../components/organisms/AssetsUser' import Web3message from '../components/organisms/Web3message' -import { User } from '../context/User' +import { User } from '../context' export default class History extends Component { public render() { diff --git a/client/src/routes/Publish/Step.tsx b/client/src/routes/Publish/Step.tsx index 06d25b2..ec940f5 100644 --- a/client/src/routes/Publish/Step.tsx +++ b/client/src/routes/Publish/Step.tsx @@ -3,7 +3,7 @@ import Input from '../../components/atoms/Form/Input' import Label from '../../components/atoms/Form/Label' import Row from '../../components/atoms/Form/Row' import Button from '../../components/atoms/Button' -import { User } from '../../context/User' +import { User } from '../../context' import Files from './Files/' import StepRegisterContent from './StepRegisterContent' import styles from './Step.module.scss' diff --git a/client/src/routes/Publish/index.tsx b/client/src/routes/Publish/index.tsx index 70670d0..eac62f3 100644 --- a/client/src/routes/Publish/index.tsx +++ b/client/src/routes/Publish/index.tsx @@ -3,7 +3,7 @@ import { Logger } from '@oceanprotocol/squid' import Route from '../../components/templates/Route' import Form from '../../components/atoms/Form/Form' import AssetModel from '../../models/AssetModel' -import { User } from '../../context/User' +import { User } from '../../context' import Web3message from '../../components/organisms/Web3message' import Step from './Step' import Progress from './Progress' diff --git a/client/src/routes/Search.tsx b/client/src/routes/Search.tsx index e8be6fb..0424e03 100644 --- a/client/src/routes/Search.tsx +++ b/client/src/routes/Search.tsx @@ -3,7 +3,7 @@ import queryString from 'query-string' import { Logger } from '@oceanprotocol/squid' import Spinner from '../components/atoms/Spinner' import Route from '../components/templates/Route' -import { User } from '../context/User' +import { User } from '../context' import Asset from '../components/molecules/Asset' import Pagination from '../components/molecules/Pagination' import styles from './Search.module.scss'