Merge pull request #34 from status-im/features/backend_tab/ens-explorer

Features/backend tab/ens explorer
This commit is contained in:
Iuri Matias 2018-08-14 16:53:09 -04:00 committed by GitHub
commit 4c8eb3e614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 782 additions and 271 deletions

View File

@ -104,11 +104,18 @@ export const contractProfile = {
failure: (error) => action(CONTRACT_PROFILE[FAILURE], {error})
};
export const MESSAGE_VERSION = createRequestTypes('MESSAGE_VERSION');
export const messageVersion = {
request: () => action(MESSAGE_VERSION[REQUEST]),
success: (messageVersion) => action(MESSAGE_VERSION[SUCCESS], {messageVersion}),
failure: (error) => action(MESSAGE_VERSION[FAILURE], {error})
export const VERSIONS = createRequestTypes('VERSIONS');
export const versions = {
request: () => action(VERSIONS[REQUEST]),
success: (versions) => action(VERSIONS[SUCCESS], {versions}),
failure: (error) => action(VERSIONS[FAILURE], {error})
};
export const PLUGINS = createRequestTypes('PLUGINS');
export const plugins = {
request: () => action(PLUGINS[REQUEST]),
success: (plugins) => action(PLUGINS[SUCCESS], {plugins}),
failure: (error) => action(PLUGINS[FAILURE], {error})
};
export const MESSAGE_SEND = createRequestTypes('MESSAGE_SEND');
@ -125,6 +132,28 @@ export const messageListen = {
failure: (error) => action(MESSAGE_LISTEN[FAILURE], {error})
};
export const ENS_RECORD = createRequestTypes('ENS_RECORD');
export const ensRecord = {
resolve: (name) => action(ENS_RECORD[REQUEST], {name}),
lookup: (address) => action(ENS_RECORD[REQUEST], {address}),
success: (record, payload) => action(ENS_RECORD[SUCCESS], {ensRecords: [Object.assign(payload, record)]}),
failure: (error) => action(ENS_RECORD[FAILURE], {error})
};
export const ENS_RECORDS = createRequestTypes('ENS_RECORDS');
export const ensRecords = {
post: (subdomain, address) => action(ENS_RECORDS[REQUEST], {subdomain, address}),
success: (record) => action(ENS_RECORDS[SUCCESS], {ensRecords: [record]}),
failure: (error) => action(ENS_RECORDS[FAILURE], {error})
};
export const FIDDLE = createRequestTypes('FIDDLE');
export const fiddle = {
request: (codeToCompile) => action(FIDDLE[REQUEST], {codeToCompile}),
success: (fiddle) => action(FIDDLE[SUCCESS], {fiddles: [fiddle]}),
failure: (error) => action(FIDDLE[FAILURE], {error})
};
// Web Socket
export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS';
export const WATCH_NEW_CONTRACT_LOGS = 'WATCH_NEW_CONTRACT_LOGS';
@ -149,11 +178,4 @@ export function initBlockHeader(){
};
}
export const FIDDLE = createRequestTypes('FIDDLE');
export const fiddle = {
request: (codeToCompile) => action(FIDDLE[REQUEST], {codeToCompile}),
success: (fiddle) => action(FIDDLE[SUCCESS], {fiddle}),
failure: (error) => action(FIDDLE[FAILURE], {error})
};

View File

@ -68,22 +68,38 @@ export function fetchContract(payload) {
return get(`/contract/${payload.contractName}`);
}
export function communicationVersion() {
return get(`/communication/version`);
export function fetchVersions() {
return get('/versions');
}
export function fetchPlugins() {
return get('/plugins');
}
export function sendMessage(payload) {
return post(`/communication/sendMessage`, payload.body);
}
export function listenToChannel(channel) {
return new WebSocket(`${constants.wsEndpoint}/communication/listenTo/${channel}`);
}
export function fetchContractProfile(payload) {
return get(`/profiler/${payload.contractName}`);
}
export function fetchEnsRecord(payload) {
if (payload.name) {
return get('/ens/resolve', {params: payload});
} else {
return get('/ens/lookup', {params: payload});
}
}
export function postEnsRecord(payload) {
return post('/ens/register', payload);
}
export function listenToChannel(channel) {
return new WebSocket(`${constants.wsEndpoint}/communication/listenTo/${channel}`);
}
export function webSocketProcess(processName) {
return new WebSocket(constants.wsEndpoint + '/process-logs/' + processName);
}

View File

@ -0,0 +1,58 @@
import React, {Component} from 'react';
import {
Alert,
Button,
Form
} from "tabler-react";
import PropTypes from 'prop-types';
class EnsLookup extends Component {
constructor(props) {
super(props);
this.state = {
address: '',
showResult: false
};
}
handleChange(e) {
this.setState({address: e.target.value, showResult: false});
}
handleLookup() {
this.setState({showResult: true});
this.props.lookup(this.state.address);
}
showResult() {
let ensRecord = this.props.ensRecords.find((record) => record.address === this.state.address);
if (ensRecord) {
return <Alert type="success">The name is: {ensRecord.name}</Alert>;
} else {
return <Alert type="danger">We could not find a name for this address</Alert>;
}
}
render(){
return (
<React.Fragment>
<h3>Lookup</h3>
<Form.FieldSet>
<Form.Group>
<Form.Input placeholder="Enter an address" onChange={e => this.handleChange(e)}/>
</Form.Group>
<Button color="primary" onClick={() => this.handleLookup()}>Lookup</Button>
{this.state.showResult && this.showResult()}
</Form.FieldSet>
</React.Fragment>
);
}
}
EnsLookup.propTypes = {
lookup: PropTypes.func,
ensRecords: PropTypes.arrayOf(PropTypes.object)
};
export default EnsLookup;

View File

@ -0,0 +1,71 @@
import React, {Component} from 'react';
import {
Alert,
Button,
Form, Grid
} from "tabler-react";
import PropTypes from 'prop-types';
class EnsRegister extends Component {
constructor(props) {
super(props);
this.state = {
subdomain: '',
address: '',
showResult: false
};
}
handleChange(e, name) {
this.setState({
showResult: false,
[name]: e.target.value
});
}
handleRegister() {
this.props.register(this.state.subdomain, this.state.address);
this.setState({showResult: true});
}
showResult() {
if (this.props.ensErrors) {
return <Alert type="danger">An error happened: {this.props.ensErrors}</Alert>;
} else {
return <Alert type="success">Successfully registered</Alert>;
}
}
render(){
return (
<React.Fragment>
<h3>Register</h3>
<Form.FieldSet>
<Grid.Row>
<Grid.Col md={6}>
<Form.Group>
<Form.Input placeholder="Enter a subdomain" onChange={e => this.handleChange(e, "subdomain")}/>
</Form.Group>
</Grid.Col>
<Grid.Col md={6}>
<Form.Group>
<Form.Input placeholder="Enter an address" onChange={e => this.handleChange(e, "address")}/>
</Form.Group>
</Grid.Col>
</Grid.Row>
<Button color="primary" onClick={() => this.handleRegister()}>Register</Button>
</Form.FieldSet>
{this.state.showResult && this.showResult()}
</React.Fragment>
);
}
}
EnsRegister.propTypes = {
register: PropTypes.func,
ensRecords: PropTypes.arrayOf(PropTypes.object),
ensErrors: PropTypes.string
};
export default EnsRegister;

View File

@ -0,0 +1,58 @@
import React, {Component} from 'react';
import {
Alert,
Button,
Form
} from "tabler-react";
import PropTypes from 'prop-types';
class EnsResolve extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
showResult: false
};
}
handleChange(e) {
this.setState({name: e.target.value, showResult: false});
}
handleResolve() {
this.setState({showResult: true});
this.props.resolve(this.state.name);
}
showResult() {
let ensRecord = this.props.ensRecords.find((record) => record.name === this.state.name);
if (ensRecord) {
return <Alert type="success">The address is: {ensRecord.address}</Alert>;
} else {
return <Alert type="danger">We could not find an address for this name</Alert>;
}
}
render(){
return (
<React.Fragment>
<h3>Resolve</h3>
<Form.FieldSet>
<Form.Group>
<Form.Input placeholder="Enter a name" onChange={e => this.handleChange(e)}/>
</Form.Group>
<Button color="primary" onClick={() => this.handleResolve()}>Resolve</Button>
{this.state.showResult && this.showResult()}
</Form.FieldSet>
</React.Fragment>
);
}
}
EnsResolve.propTypes = {
resolve: PropTypes.func,
ensRecords: PropTypes.arrayOf(PropTypes.object)
};
export default EnsResolve;

View File

@ -10,9 +10,18 @@ import AccountsContainer from '../containers/AccountsContainer';
import AccountContainer from '../containers/AccountContainer';
import BlocksContainer from '../containers/BlocksContainer';
import BlockContainer from '../containers/BlockContainer';
import CommunicationContainer from '../containers/CommunicationContainer';
import TransactionsContainer from '../containers/TransactionsContainer';
import TransactionContainer from '../containers/TransactionContainer';
import CommunicationContainer from '../containers/CommunicationContainer';
import EnsContainer from '../containers/EnsContainer';
const groupItems = [
{to: "/embark/explorer/accounts", icon: "users", value: "Accounts"},
{to: "/embark/explorer/blocks", icon: "book-open", value: "Blocks"},
{to: "/embark/explorer/transactions", icon: "activity", value: "Transactions"},
{to: "/embark/explorer/communication", icon: "phone-call", value: "Communication"},
{to: "/embark/explorer/ens", icon: "disc", value: "ENS"}
];
const className = "d-flex align-items-center";
@ -22,38 +31,17 @@ const ExplorerLayout = () => (
<Page.Title className="my-5">Explorer</Page.Title>
<div>
<List.Group transparent={true}>
<List.GroupItem
className={className}
to="/embark/explorer/accounts"
icon="users"
RootComponent={withRouter(NavLink)}
>
Accounts
</List.GroupItem>
<List.GroupItem
className={className}
to="/embark/explorer/blocks"
icon="book-open"
RootComponent={withRouter(NavLink)}
>
Blocks
</List.GroupItem>
<List.GroupItem
className={className}
to="/embark/explorer/transactions"
icon="activity"
RootComponent={withRouter(NavLink)}
>
Transactions
</List.GroupItem>
<List.GroupItem
className={className}
to="/embark/explorer/communication"
icon="phone-call"
RootComponent={withRouter(NavLink)}
>
Communication
</List.GroupItem>
{groupItems.map((groupItem) => (
<List.GroupItem
key={groupItem.value}
className={className}
to={groupItem.to}
icon={groupItem.icon}
RootComponent={withRouter(NavLink)}
>
{groupItem.value}
</List.GroupItem>
))}
</List.Group>
</div>
</Grid.Col>
@ -63,9 +51,10 @@ const ExplorerLayout = () => (
<Route exact path="/embark/explorer/accounts/:address" component={AccountContainer} />
<Route exact path="/embark/explorer/blocks" component={BlocksContainer} />
<Route exact path="/embark/explorer/blocks/:blockNumber" component={BlockContainer} />
<Route exact path="/embark/explorer/communication" component={CommunicationContainer} />
<Route exact path="/embark/explorer/transactions" component={TransactionsContainer} />
<Route exact path="/embark/explorer/transactions/:hash" component={TransactionContainer} />
<Route exact path="/embark/explorer/communication" component={CommunicationContainer} />
<Route exact path="/embark/explorer/ens" component={EnsContainer} />
</Switch>
</Grid.Col>
</Grid.Row>

View File

@ -0,0 +1,34 @@
import PropTypes from "prop-types";
import React from 'react';
import {Grid, Card} from 'tabler-react';
const Version = ({version}) => (
<Grid.Col sm={6} lg={3}>
<Card className="p-3">
<div className="d-flex align-items-center">
<span className="stamp stamp-md mr-3 bg-info">
<i className="fe fa-cube"></i>
</span>
<div>
<h4 className="text-capitalize m-0">{version.name}: {version.value}</h4>
</div>
</div>
</Card>
</Grid.Col>
);
Version.propTypes = {
version: PropTypes.object
};
const Versions = ({versions}) => (
<Grid.Row cards>
{versions.map((version) => <Version key={version.name} version={version} />)}
</Grid.Row>
);
Versions.propTypes = {
versions: PropTypes.arrayOf(PropTypes.object)
};
export default Versions;

View File

@ -6,13 +6,22 @@ import React, {Component} from 'react';
import history from '../history';
import Layout from '../components/Layout';
import routes from '../routes';
import {contracts as contractsAction, initBlockHeader, processes as processesAction} from '../actions';
import {
initBlockHeader,
contracts as contractsAction,
processes as processesAction,
versions as versionsAction,
plugins as pluginsAction
} from '../actions';
class AppContainer extends Component {
componentDidMount() {
this.props.initBlockHeader();
this.props.fetchProcesses();
this.props.fetchContracts();
this.props.fetchVersions();
this.props.fetchPlugins();
}
render() {
@ -29,7 +38,9 @@ class AppContainer extends Component {
AppContainer.propTypes = {
initBlockHeader: PropTypes.func,
fetchContracts: PropTypes.func,
fetchProcesses: PropTypes.func
fetchProcesses: PropTypes.func,
fetchPlugins: PropTypes.func,
fetchVersions: PropTypes.func
};
export default connect(
@ -37,6 +48,8 @@ export default connect(
{
initBlockHeader,
fetchProcesses: processesAction.request,
fetchContracts: contractsAction.request
fetchContracts: contractsAction.request,
fetchVersions: versionsAction.request,
fetchPlugins: pluginsAction.request
},
)(AppContainer);

View File

@ -1,17 +1,12 @@
import PropTypes from "prop-types";
import React, {Component} from 'react';
import connect from "react-redux/es/connect/connect";
import {Alert, Loader, Page} from 'tabler-react';
import {messageSend, messageListen, messageVersion} from "../actions";
import {Alert, Page} from 'tabler-react';
import {messageSend, messageListen} from "../actions";
import Communication from "../components/Communication";
import Loading from "../components/Loading";
import {getMessageVersion, getMessages, getMessageChannels} from "../reducers/selectors";
import {getMessages, getMessageChannels, isOldWeb3, isWeb3Enabled} from "../reducers/selectors";
class CommunicationContainer extends Component {
componentDidMount() {
this.props.communicationVersion();
}
sendMessage(topic, message) {
this.props.messageSend({topic, message});
}
@ -20,27 +15,29 @@ class CommunicationContainer extends Component {
this.props.messageListen(channel);
}
render() {
let isEnabledMessage = '';
if (this.props.messageVersion === undefined || this.props.messageVersion === null) {
isEnabledMessage =
<Alert bsStyle="secondary "><Loader/> Checking Whisper support, please wait</Alert>;
} else if (!this.props.messageVersion) {
isEnabledMessage = <Alert type="warning">The node you are using does not support Whisper</Alert>;
} else if (this.props.messageVersion === -1) {
isEnabledMessage = <Alert type="warning">The node uses an unsupported version of Whisper</Alert>;
}
web3DisabledWarning() {
return <Alert type="warning">The node you are using does not support Whisper</Alert>;
}
if (!this.props.messages) {
return <Loading/>;
}
web3Enabled() {
return this.props.isOldWeb3 ? this.web3DisabledWarning() : this.showCommunication();
}
web3OldWarning() {
return <Alert type="warning">The node uses an unsupported version of Whisper</Alert>;
}
showCommunication() {
return <Communication listenToMessages={(channel) => this.listenToChannel(channel)}
sendMessage={(channel, message) => this.sendMessage(channel, message)}
channels={this.props.messages}
subscriptions={this.props.messageChannels}/>;
}
render() {
return (
<Page.Content title="Communication explorer">
{isEnabledMessage}
<Communication listenToMessages={(channel) => this.listenToChannel(channel)}
sendMessage={(channel, message) => this.sendMessage(channel, message)}
channels={this.props.messages}
subscriptions={this.props.messageChannels}/>
{this.props.isWeb3Enabled ? this.web3Enabled() : this.web3DisabledWarning()}
</Page.Content>
);
}
@ -49,8 +46,8 @@ class CommunicationContainer extends Component {
CommunicationContainer.propTypes = {
messageSend: PropTypes.func,
messageListen: PropTypes.func,
communicationVersion: PropTypes.func,
messageVersion: PropTypes.number,
isOldWeb3: PropTypes.bool,
isWeb3Enabled: PropTypes.bool,
messages: PropTypes.object,
messageChannels: PropTypes.array
};
@ -59,7 +56,8 @@ function mapStateToProps(state) {
return {
messages: getMessages(state),
messageChannels: getMessageChannels(state),
messageVersion: getMessageVersion(state)
isOldWeb3: isOldWeb3(state),
isWeb3Enabled: isWeb3Enabled(state)
};
}
@ -67,8 +65,7 @@ export default connect(
mapStateToProps,
{
messageSend: messageSend.request,
messageListen: messageListen.request,
communicationVersion: messageVersion.request
messageListen: messageListen.request
}
)(CommunicationContainer);

View File

@ -0,0 +1,61 @@
import PropTypes from "prop-types";
import React, {Component} from 'react';
import connect from "react-redux/es/connect/connect";
import {Alert, Page} from "tabler-react";
import {ensRecord, ensRecords} from "../actions";
import EnsRegister from "../components/EnsRegister";
import EnsLookup from "../components/EnsLookup";
import EnsResolve from "../components/EnsResolve";
import {getEnsRecords, isEnsEnabled, getEnsErrors} from "../reducers/selectors";
class EnsContainer extends Component {
showEns() {
return (
<React.Fragment>
<EnsLookup lookup={this.props.lookup} ensRecords={this.props.ensRecords}/>
<EnsResolve resolve={this.props.resolve} ensRecords={this.props.ensRecords}/>
<EnsRegister register={this.props.register} ensRecords={this.props.ensRecords} ensErrors={this.props.ensErrors}/>
</React.Fragment>
);
}
showWarning() {
return <Alert type="warning">Please enable Ens in Embark first</Alert>;
}
render() {
return (
<Page.Content title="Ens">
{this.props.isEnsEnabled ? this.showEns() : this.showWarning()}
</Page.Content>
);
}
}
EnsContainer.propTypes = {
ensRecords: PropTypes.array,
resolve: PropTypes.func,
lookup: PropTypes.func,
register: PropTypes.func,
isEnsEnabled: PropTypes.bool,
ensErrors: PropTypes.string
};
function mapStateToProps(state) {
return {
ensRecords: getEnsRecords(state),
ensErrors: getEnsErrors(state),
isEnsEnabled: isEnsEnabled(state)
};
}
export default connect(
mapStateToProps,
{
resolve: ensRecord.resolve,
lookup: ensRecord.lookup,
register: ensRecords.post
}
)(EnsContainer);

View File

@ -6,8 +6,9 @@ import {Page} from "tabler-react";
import {commands as commandsAction} from "../actions";
import DataWrapper from "../components/DataWrapper";
import Processes from '../components/Processes';
import Versions from '../components/Versions';
import Console from '../components/Console';
import {getProcesses, getCommands} from "../reducers/selectors";
import {getProcesses, getCommands, getVersions} from "../reducers/selectors";
class HomeContainer extends Component {
render() {
@ -17,6 +18,9 @@ class HomeContainer extends Component {
<DataWrapper shouldRender={this.props.processes.length > 0 } {...this.props} render={({processes}) => (
<Processes processes={processes} />
)} />
<DataWrapper shouldRender={this.props.versions.length > 0 } {...this.props} render={({versions}) => (
<Versions versions={versions} />
)} />
<Console postCommand={this.props.postCommand} commands={this.props.commands} />
</React.Fragment>
);
@ -25,12 +29,16 @@ class HomeContainer extends Component {
HomeContainer.propTypes = {
processes: PropTypes.arrayOf(PropTypes.object),
versions: PropTypes.arrayOf(PropTypes.object),
postCommand: PropTypes.func,
commands: PropTypes.arrayOf(PropTypes.object)
commands: PropTypes.arrayOf(PropTypes.object),
error: PropTypes.string,
loading: PropTypes.bool
};
function mapStateToProps(state) {
return {
versions: getVersions(state),
processes: getProcesses(state),
commands: getCommands(state),
error: state.errorMessage,

View File

@ -42,7 +42,7 @@ function mapStateToProps(state) {
}
TransactionsContainer.propTypes = {
transactions: PropTypes.object,
transactions: PropTypes.arrayOf(PropTypes.object),
fetchTransactions: PropTypes.func,
error: PropTypes.string,
loading: PropTypes.bool

View File

@ -1,7 +1,8 @@
import {combineReducers} from 'redux';
import {REQUEST} from "../actions";
import {REQUEST, SUCCESS} from "../actions";
const BN_FACTOR = 10000;
const voidAddress = '0x0000000000000000000000000000000000000000';
const entitiesDefaultState = {
accounts: [],
@ -15,8 +16,10 @@ const entitiesDefaultState = {
commands: [],
messages: [],
messageChannels: [],
messageVersion: null,
fiddle: null
fiddles: [],
versions: [],
plugins: [],
ensRecords: []
};
const sorter = {
@ -54,6 +57,11 @@ const filtrer = {
return index === self.findIndex((t) => (
t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex
));
},
ensRecords: function(record, index, self) {
return record.name && record.address && record.address !== voidAddress && index === self.findIndex((r) => (
r.address=== record.address && r.name === record.name
));
}
};
@ -61,10 +69,7 @@ function entities(state = entitiesDefaultState, action) {
for (let name of Object.keys(state)) {
let filter = filtrer[name] || (() => true);
let sort = sorter[name] || (() => true);
if (action[name] && !Array.isArray(action[name])) {
return {...state, [name]: action[name]};
}
if (action[name] && (!Array.isArray(action[name]) || action[name].length > 1)) {
if (action[name] && action[name].length > 1) {
return {...state, [name]: [...action[name], ...state[name]].filter(filter).sort(sort)};
}
if (action[name] && action[name].length === 1) {
@ -90,6 +95,20 @@ function errorMessage(state = null, action) {
return action.error || state;
}
/* eslint multiline-ternary: "off" */
function errorEntities(state = {}, action) {
if (!action.type.endsWith(SUCCESS)) {
return state;
}
let newState = {};
for (let name of Object.keys(entitiesDefaultState)) {
if (action[name] && action[name].length > 0) {
newState[name] = action[name][0].error;
}
}
return {...state, ...newState};
}
function loading(_state = false, action) {
return action.type.endsWith(REQUEST);
}
@ -97,7 +116,8 @@ function loading(_state = false, action) {
const rootReducer = combineReducers({
entities,
loading,
errorMessage
errorMessage,
errorEntities
});
export default rootReducer;

View File

@ -62,8 +62,17 @@ export function getContractProfile(state, contractName) {
return state.entities.contractProfiles.find((contractProfile => contractProfile.name === contractName));
}
export function getMessageVersion(state) {
return state.entities.messageVersion;
export function getVersions(state) {
return state.entities.versions;
}
export function isWeb3Enabled(state) {
return Boolean(state.entities.versions.find((version) => version.name === 'web3'));
}
export function isOldWeb3(state) {
const web3 = state.entities.versions.find((version) => version.name === 'web3');
return web3 && parseInt(web3[0], 10) === 0;
}
export function getMessageChannels(state) {
@ -81,6 +90,18 @@ export function getMessages(state) {
return messages;
}
export function getFiddle(state){
return state.entities.fiddle;
export function getFiddle(state) {
return state.entities.fiddles[state.entities.fiddles.length - 1];
}
export function getEnsRecords(state) {
return state.entities.ensRecords;
}
export function getEnsErrors(state) {
return state.errorEntities.ensRecords;
}
export function isEnsEnabled(state) {
return Boolean(state.entities.plugins.find((plugin) => plugin.name === 'ens'));
}

View File

@ -4,8 +4,8 @@ import {eventChannel} from 'redux-saga';
import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects';
const {account, accounts, block, blocks, transaction, transactions, processes, commands, processLogs,
contracts, contract, contractProfile, messageSend, messageVersion, messageListen, contractLogs,
fiddle} = actions;
contracts, contract, contractProfile, messageSend, versions, plugins, messageListen, fiddle,
ensRecord, ensRecords, contractLogs} = actions;
function *doRequest(entity, apiFn, payload) {
const {response, error} = yield call(apiFn, payload);
@ -16,6 +16,8 @@ function *doRequest(entity, apiFn, payload) {
}
}
export const fetchPlugins = doRequest.bind(null, plugins, api.fetchPlugins);
export const fetchVersions = doRequest.bind(null, versions, api.fetchVersions);
export const fetchAccount = doRequest.bind(null, account, api.fetchAccount);
export const fetchBlock = doRequest.bind(null, block, api.fetchBlock);
export const fetchTransaction = doRequest.bind(null, transaction, api.fetchTransaction);
@ -30,6 +32,9 @@ export const fetchContracts = doRequest.bind(null, contracts, api.fetchContracts
export const fetchContract = doRequest.bind(null, contract, api.fetchContract);
export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile);
export const fetchFiddle = doRequest.bind(null, fiddle, api.fetchFiddle);
export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage);
export const fetchEnsRecord = doRequest.bind(null, ensRecord, api.fetchEnsRecord);
export const postEnsRecord = doRequest.bind(null, ensRecords, api.postEnsRecord);
export function *watchFetchTransaction() {
yield takeEvery(actions.TRANSACTION[actions.REQUEST], fetchTransaction);
@ -83,6 +88,34 @@ export function *watchFetchContractProfile() {
yield takeEvery(actions.CONTRACT_PROFILE[actions.REQUEST], fetchContractProfile);
}
export function *watchFetchVersions() {
yield takeEvery(actions.VERSIONS[actions.REQUEST], fetchVersions);
}
export function *watchFetchPlugins() {
yield takeEvery(actions.PLUGINS[actions.REQUEST], fetchPlugins);
}
export function *watchSendMessage() {
yield takeEvery(actions.MESSAGE_SEND[actions.REQUEST], sendMessage);
}
export function *watchFetchEnsRecord() {
yield takeEvery(actions.ENS_RECORD[actions.REQUEST], fetchEnsRecord);
}
export function *watchPostEnsRecords() {
yield takeEvery(actions.ENS_RECORDS[actions.REQUEST], postEnsRecord);
}
export function *watchListenToMessages() {
yield takeEvery(actions.MESSAGE_LISTEN[actions.REQUEST], listenToMessages);
}
export function *watchFetchFiddle() {
yield takeEvery(actions.FIDDLE[actions.REQUEST], fetchFiddle);
}
function createChannel(socket) {
return eventChannel(emit => {
socket.onmessage = ((message) => {
@ -134,12 +167,6 @@ export function *watchListenToContractLogs() {
yield takeEvery(actions.WATCH_NEW_CONTRACT_LOGS, listenToContractLogs);
}
export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage);
export function *watchSendMessage() {
yield takeEvery(actions.MESSAGE_SEND[actions.REQUEST], sendMessage);
}
export function *listenToMessages(action) {
const socket = api.listenToChannel(action.messageChannels[0]);
const channel = yield call(createChannel, socket);
@ -149,20 +176,6 @@ export function *listenToMessages(action) {
}
}
export function *watchListenToMessages() {
yield takeEvery(actions.MESSAGE_LISTEN[actions.REQUEST], listenToMessages);
}
export const fetchCommunicationVersion = doRequest.bind(null, messageVersion, api.communicationVersion);
export function *watchCommunicationVersion() {
yield takeEvery(actions.MESSAGE_VERSION[actions.REQUEST], fetchCommunicationVersion);
}
export function *watchFetchFiddle() {
yield takeEvery(actions.FIDDLE[actions.REQUEST], fetchFiddle);
}
export default function *root() {
yield all([
fork(watchInitBlockHeader),
@ -176,7 +189,8 @@ export default function *root() {
fork(watchFetchBlock),
fork(watchFetchTransactions),
fork(watchPostCommand),
fork(watchCommunicationVersion),
fork(watchFetchVersions),
fork(watchFetchPlugins),
fork(watchFetchBlocks),
fork(watchFetchContracts),
fork(watchListenToMessages),
@ -184,7 +198,8 @@ export default function *root() {
fork(watchFetchContract),
fork(watchFetchTransaction),
fork(watchFetchContractProfile),
fork(watchFetchFiddle)
fork(watchFetchFiddle),
fork(watchFetchEnsRecord),
fork(watchPostEnsRecords)
]);
}

View File

@ -16,21 +16,19 @@ var Plugins = function(options) {
};
Plugins.prototype.loadPlugins = function() {
var pluginConfig;
for (var pluginName in this.pluginList) {
pluginConfig = this.pluginList[pluginName];
for (let pluginName in this.pluginList) {
let pluginConfig = this.pluginList[pluginName];
this.loadPlugin(pluginName, pluginConfig);
}
};
Plugins.prototype.listPlugins = function() {
const list = [];
this.plugins.forEach(plugin => {
return this.plugins.reduce((list, plugin) => {
if (plugin.loaded) {
list.push(plugin.name);
}
});
return list;
return list;
}, []);
};
// for services that act as a plugin but have core functionality

View File

@ -0,0 +1,106 @@
const namehash = require('eth-ens-namehash');
const reverseAddrSuffix = '.addr.reverse';
const voidAddress = '0x0000000000000000000000000000000000000000';
const NoDecodeAddrError = 'Error: Couldn\'t decode address from ABI: 0x';
const NoDecodeStringError = 'ERROR: The returned value is not a convertible string: 0x0';
function registerSubDomain(ens, registrar, resolver, defaultAccount, subdomain, rootDomain, reverseNode, address, logger, callback) {
const subnode = namehash.hash(subdomain);
const node = namehash.hash(`${subdomain}.${rootDomain}`);
const toSend = registrar.methods.register(subnode, defaultAccount);
let transaction;
toSend.estimateGas()
// Register domain
.then(gasEstimated => {
return toSend.send({gas: gasEstimated + 1000, from: defaultAccount});
})
// Set resolver for the node
.then(transac => {
if (transac.status !== "0x1" && transac.status !== "0x01" && transac.status !== true) {
logger.warn('Failed transaction', transac);
return callback('Failed to register. Check gas cost.');
}
transaction = transac;
return ens.methods.setResolver(node, resolver.options.address).send({from: defaultAccount});
})
// Set address for node
.then(_result => {
return resolver.methods.setAddr(node, address).send({from: defaultAccount});
})
// Set resolver for the reverse node
.then(_result => {
return ens.methods.setResolver(reverseNode, resolver.options.address).send({from: defaultAccount});
})
// Set name for reverse node
.then(_result => {
return resolver.methods.setName(reverseNode, subdomain + '.embark.eth').send({from: defaultAccount});
})
.then(_result => {
callback(null, transaction);
})
.catch(err => {
logger.error(err);
callback('Failed to register with error: ' + (err.message || err));
});
}
function lookupAddress(address, ens, utils, createResolverContract, callback) {
if (address.startsWith("0x")) {
address = address.slice(2);
}
let node = utils.soliditySha3(address.toLowerCase() + reverseAddrSuffix);
function cb(err, name) {
if (err === NoDecodeStringError || err === NoDecodeAddrError) {
return callback('Address does not resolve to name. Try syncing chain.');
}
return callback(err, name);
}
return ens.methods.resolver(node).call((err, resolverAddress) => {
if (err) {
return cb(err);
}
if (resolverAddress === voidAddress) {
return cb('Address not associated to a resolver');
}
createResolverContract(resolverAddress, (_, resolverContract) => {
resolverContract.methods.name(node).call(cb);
});
});
}
function resolveName(name, ens, createResolverContract, callback) {
let node = namehash.hash(name);
function cb(err, addr) {
if (err === NoDecodeAddrError) {
return callback(name + " is not registered", "0x");
}
callback(err, addr);
}
return ens.methods.resolver(node).call((err, resolverAddress) => {
if (err) {
return cb(err);
}
if (resolverAddress === voidAddress) {
return cb('Name not yet registered');
}
createResolverContract(resolverAddress, (_, resolverContract) => {
resolverContract.methods.addr(node).call(cb);
});
});
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
registerSubDomain,
resolveName,
lookupAddress
};
}

View File

@ -1,4 +1,4 @@
/*global EmbarkJS, web3, registerSubDomain, namehash*/
/*global EmbarkJS, web3, lookupAddress, resolveName, registerSubDomain, reverseAddrSuffix*/
let __embarkENS = {};
@ -132,10 +132,6 @@ __embarkENS.resolverInterface = [
];
const providerNotSetError = 'ENS provider not set';
const NoDecodeAddrError = 'Error: Couldn\'t decode address from ABI: 0x';
const NoDecodeStringError = 'ERROR: The returned value is not a convertible string: 0x0';
const reverseAddrSuffix = '.addr.reverse';
const voidAddress = '0x0000000000000000000000000000000000000000';
__embarkENS.registryAddresses = {
// Mainnet
@ -171,30 +167,17 @@ __embarkENS.setProvider = function (config) {
});
};
const createResolverContract = function (address, callback) {
callback(null, new EmbarkJS.Contract({abi: __embarkENS.resolverInterface, address}));
};
__embarkENS.resolve = function (name, callback) {
callback = callback || function () {};
if (!this.ens) {
return callback(providerNotSetError);
}
let node = namehash.hash(name);
function cb(err, addr) {
if (err === NoDecodeAddrError) {
return callback(name + " is not registered", "0x");
}
callback(err, addr);
}
return this.ens.methods.resolver(node).call((err, resolverAddress) => {
if (err) {
return cb(err);
}
if (resolverAddress === voidAddress) {
return cb('Name not yet registered');
}
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress});
resolverContract.methods.addr(node).call(cb);
});
return resolveName(name, this.ens, createResolverContract, callback);
};
__embarkENS.lookup = function (address, callback) {
@ -202,28 +185,8 @@ __embarkENS.lookup = function (address, callback) {
if (!this.ens) {
return callback(providerNotSetError);
}
if (address.startsWith("0x")) {
address = address.slice(2);
}
let node = web3.utils.soliditySha3(address.toLowerCase() + reverseAddrSuffix);
function cb(err, name) {
if (err === NoDecodeStringError || err === NoDecodeAddrError) {
return callback('Address does not resolve to name. Try syncing chain.');
}
return callback(err, name);
}
return this.ens.methods.resolver(node).call((err, resolverAddress) => {
if (err) {
return cb(err);
}
if (resolverAddress === voidAddress) {
return cb('Address not associated to a resolver');
}
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress});
resolverContract.methods.name(node).call(cb);
});
return lookupAddress(address, this.ens, web3.utils, createResolverContract.bind(this), callback);
};
__embarkENS.registerSubDomain = function (name, address, callback) {
@ -240,8 +203,9 @@ __embarkENS.registerSubDomain = function (name, address, callback) {
}
// Register function generated by the index
registerSubDomain(this.ens, this.registrar, this.resolver, web3.eth.defaultAccount, name, this.registration.rootDomain,
web3.utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix), address, console, callback);
registerSubDomain(this.ens, this.registrar, this.resolver, web3.eth.defaultAccount,
name, this.registration.rootDomain, web3.utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix),
address, console, callback);
};
__embarkENS.isAvailable = function () {

View File

@ -2,6 +2,7 @@ const fs = require('../../core/fs.js');
const utils = require('../../utils/utils.js');
const namehash = require('eth-ens-namehash');
const async = require('async');
const ENSFunctions = require('./ENSFunctions');
const reverseAddrSuffix = '.addr.reverse';
@ -16,10 +17,10 @@ class ENS {
this.addENSToEmbarkJS();
this.configureContracts();
this.registerEvents();
this.registerActionForEvents();
}
registerEvents() {
registerActionForEvents() {
const self = this;
self.embark.registerActionForEvent("contracts:deploy:afterAll", (cb) => {
async.parallel([
@ -51,58 +52,151 @@ class ENS {
resolverAddress: results[2].deployedAddress
};
self.addSetProvider(config);
self.registerAPI(config);
if (!self.env === 'development' || !self.registration || !self.registration.subdomains || !Object.keys(self.registration.subdomains).length) {
return cb();
}
self.registerConfigDomains(config, cb);
self.registerConfigSubdomains(config, cb);
});
});
}
registerConfigDomains(config, cb) {
registerAPI(config) {
let self = this;
const createInternalResolverContract = function(resolverAddress, callback) {
this.createResolverContract({resolverAbi: config.resolverAbi, resolverAddress}, callback);
};
self.embark.registerAPICall(
'get',
'/embark-api/ens/resolve',
(req, res) => {
async.waterfall([
self.createRegistryContract.bind(this, config),
function(ens, callback) {
ENSFunctions.resolveName(req.query.name, ens, createInternalResolverContract.bind(self), callback);
}
], function(error, address) {
if (error) {
return res.send({error: error.message});
}
res.send({address});
});
}
);
self.embark.registerAPICall(
'get',
'/embark-api/ens/lookup',
(req, res) => {
async.waterfall([
self.createRegistryContract.bind(this, config),
function(ens, callback) {
ENSFunctions.lookupAddress(req.query.address, ens, utils, createInternalResolverContract.bind(self), callback);
}
], function(error, name) {
if (error) {
return res.send({error: error || error.message});
}
res.send({name});
});
}
);
self.embark.registerAPICall(
'post',
'/embark-api/ens/register',
(req, res) => {
self.registerSubdomain(req.body.subdomain, req.body.address, config, (error) => {
if (error) {
return res.send({error: error || error.message});
}
res.send({name: `${req.body.subdomain}.${self.registration.rootDomain}`, address: req.body.address});
});
}
);
}
registerConfigSubdomains(config, callback) {
async.each(Object.keys(this.registration.subdomains), (subdomain, eachCb) => {
this.registerSubdomain(subdomain, this.registration.subdomains[subdomain], config, eachCb);
}, callback);
}
registerSubdomain(subdomain, address, config, callback) {
const self = this;
const register = require('./register');
self.events.request("blockchain:defaultAccount:get", (defaultAccount) => {
async.parallel([
function createRegistryContract(paraCb) {
self.events.request("blockchain:contract:create",
{abi: config.registryAbi, address: config.registryAddress},
(registry) => {
paraCb(null, registry);
});
},
function createRegistrarContract(paraCb) {
self.events.request("blockchain:contract:create",
{abi: config.registrarAbi, address: config.registrarAddress},
(registrar) => {
paraCb(null, registrar);
});
},
function createResolverContract(paraCb) {
self.events.request("blockchain:contract:create",
{abi: config.resolverAbi, address: config.resolverAddress},
(resolver) => {
paraCb(null, resolver);
});
}
], function (err, contracts) {
async.parallel({
ens: self.createRegistryContract.bind(this, config),
registrar: self.createRegistrarContract.bind(this, config),
resolver: self.createResolverContract.bind(this, config)
}, function (err, contracts) {
if (err) {
return cb(err);
return callback(err);
}
const [ens, registrar, resolver] = contracts;
async.each(Object.keys(self.registration.subdomains), (subDomainName, eachCb) => {
const address = self.registration.subdomains[subDomainName];
const reverseNode = utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix);
register(ens, registrar, resolver, defaultAccount, subDomainName, self.registration.rootDomain,
reverseNode, address, self.logger, eachCb);
}, cb);
const {ens, registrar, resolver} = contracts;
const reverseNode = utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix);
ENSFunctions.registerSubDomain(ens, registrar, resolver, defaultAccount, subdomain,
self.registration.rootDomain, reverseNode, address, self.logger, callback);
});
});
}
registerSubdomains(subdomains, config, callback) {
const self = this;
self.events.request("blockchain:defaultAccount:get", (defaultAccount) => {
async.parallel({
ens: self.createRegistryContract.bind(this, config),
registrar: self.createRegistrarContract.bind(this, config),
resolver: self.createResolverContract.bind(this, config)
}, function (err, contracts) {
if (err) {
return callback(err);
}
const {ens, registrar, resolver} = contracts;
async.each(Object.keys(subdomains), (subdomainName, eachCb) => {
const address = subdomains[subdomainName];
const reverseNode = utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix);
ENSFunctions.registerSubDomain(ens, registrar, resolver, defaultAccount, subdomainName,
self.registration.rootDomain, reverseNode, address, self.logger, eachCb);
}, callback);
});
});
}
createRegistryContract(config, callback) {
this.events.request("blockchain:contract:create", {
abi: config.registryAbi,
address: config.registryAddress
}, (registry) => {
callback(null, registry);
});
}
createRegistrarContract(config, callback) {
this.events.request("blockchain:contract:create", {
abi: config.registrarAbi,
address: config.registrarAddress
}, (registrar) => {
callback(null, registrar);
});
}
createResolverContract(config, callback) {
this.events.request("blockchain:contract:create", {
abi: config.resolverAbi,
address: config.resolverAddress
}, (resolver) => {
callback(null, resolver);
});
}
addENSToEmbarkJS() {
const self = this;
// TODO: make this a shouldAdd condition
@ -124,7 +218,7 @@ class ENS {
}
});
let code = fs.readFileSync(utils.joinPath(__dirname, 'register.js')).toString();
let code = fs.readFileSync(utils.joinPath(__dirname, 'ENSFunctions.js')).toString();
code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'embarkjs.js')).toString();
code += "\nEmbarkJS.Names.registerProvider('ens', __embarkENS);";

View File

@ -1,46 +0,0 @@
const namehash = require('eth-ens-namehash');
function registerSubDomain(ens, registrar, resolver, defaultAccount, subdomain, rootDomain, reverseNode, address, logger, callback) {
const subnode = namehash.hash(subdomain);
const node = namehash.hash(`${subdomain}.${rootDomain}`);
const toSend = registrar.methods.register(subnode, defaultAccount);
let transaction;
toSend.estimateGas()
// Register domain
.then(gasEstimated => {
return toSend.send({gas: gasEstimated + 1000, from: defaultAccount});
})
// Set resolver for the node
.then(transac => {
if (transac.status !== "0x1" && transac.status !== "0x01" && transac.status !== true) {
logger.warn('Failed transaction', transac);
return callback('Failed to register. Check gas cost.');
}
transaction = transac;
return ens.methods.setResolver(node, resolver.options.address).send({from: defaultAccount});
})
// Set address for node
.then(_result => {
return resolver.methods.setAddr(node, address).send({from: defaultAccount});
})
// Set resolver for the reverse node
.then(_result => {
return ens.methods.setResolver(reverseNode, resolver.options.address).send({from: defaultAccount});
})
// Set name for reverse node
.then(_result => {
return resolver.methods.setName(reverseNode, subdomain + '.embark.eth').send({from: defaultAccount});
})
.then(_result => {
callback(null, transaction);
})
.catch(err => {
logger.error(err);
callback('Failed to register with error: ' + (err.message || err));
});
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = registerSubDomain;
}

View File

@ -11,6 +11,7 @@ class LibraryManager {
this.determineVersions();
this.registerCommands();
this.registerAPICommands();
this.listenToCommandsToGetVersions();
this.listenToCommandsToGetLibrary();
}
@ -49,6 +50,18 @@ class LibraryManager {
});
}
registerAPICommands() {
const self = this;
self.embark.registerAPICall(
'get',
'/embark-api/versions',
(req, res) => {
const versions = Object.keys(self.versions).map((name) => ({value: self.versions[name], name}));
res.send(versions);
}
);
}
listenToCommandsToGetVersions() {
const self = this;
for (let libName in this.versions) {

View File

@ -43,6 +43,13 @@ class Server {
if (self.plugins) {
let apiCalls = self.plugins.getPluginsProperty("apiCalls", "apiCalls");
app.get('/embark-api/plugins', function(req, res) {
res.send(JSON.stringify(self.plugins.plugins.map((plugin) => {
return {name: plugin.name};
})));
});
for (let apiCall of apiCalls) {
console.dir("adding " + apiCall.method + " " + apiCall.endpoint);
app[apiCall.method].apply(app, [apiCall.endpoint, apiCall.cb]);

View File

@ -178,14 +178,6 @@ class Whisper {
ws.send(JSON.stringify(result));
});
});
self.embark.registerAPICall(
'get',
'/embark-api/communication/version',
(req, res) => {
res.send(self.isOldWeb3 ? -1 : self.version || 0);
}
);
});
}
}