diff --git a/js/src/api/api.js b/js/src/api/api.js index 417af17fe..cb86cde66 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -20,7 +20,7 @@ import Contract from './contract'; import { PromiseProvider, Http as HttpProvider, PostMessage as PostMessageProvider, WsSecure as WsSecureProvider } from './provider'; import { Http as HttpTransport, WsSecure as WsSecureTransport } from './transport'; -import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'; +import { Db, Eth, Parity, Net, Personal, Shell, Shh, Signer, Trace, Web3 } from './rpc'; import Subscriptions from './subscriptions'; import Pubsub from './pubsub'; import util from './util'; @@ -49,6 +49,7 @@ export default class Api extends EventEmitter { this._net = new Net(this._provider); this._parity = new Parity(this._provider); this._personal = new Personal(this._provider); + this._shell = new Shell(this._provider); this._shh = new Shh(this._provider); this._signer = new Signer(this._provider); this._trace = new Trace(this._provider); @@ -105,6 +106,10 @@ export default class Api extends EventEmitter { return this._provider.provider; } + get shell () { + return this._shell; + } + get shh () { return this._shh; } diff --git a/js/src/api/rpc/index.js b/js/src/api/rpc/index.js index c8fa0032b..8589437f8 100644 --- a/js/src/api/rpc/index.js +++ b/js/src/api/rpc/index.js @@ -19,6 +19,7 @@ export Eth from './eth'; export Parity from './parity'; export Net from './net'; export Personal from './personal'; +export Shell from './shell'; export Shh from './shh'; export Signer from './signer'; export Trace from './trace'; diff --git a/js/src/shell/Dapps/SelectMethods/index.js b/js/src/api/rpc/shell/index.js similarity index 94% rename from js/src/shell/Dapps/SelectMethods/index.js rename to js/src/api/rpc/shell/index.js index c0dfefd23..e832a2118 100644 --- a/js/src/shell/Dapps/SelectMethods/index.js +++ b/js/src/api/rpc/shell/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './selectMethods'; +export default from './shell'; diff --git a/js/src/api/rpc/shell/shell.js b/js/src/api/rpc/shell/shell.js new file mode 100644 index 000000000..6069a88bf --- /dev/null +++ b/js/src/api/rpc/shell/shell.js @@ -0,0 +1,46 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default class Shell { + constructor (provider) { + this._provider = provider; + } + + getApps (retrieveAll = false) { + return this._provider + .send('shell_getApps', retrieveAll); + } + + getFilteredMethods () { + return this._provider + .send('shell_getFilteredMethods'); + } + + getMethodPermissions () { + return this._provider + .send('shell_getMethodPermissions'); + } + + setMethodPermissions (permissions) { + return this._provider + .send('shell_setMethodPermissions', permissions); + } + + setAppVisibility (appId, state) { + return this._provider + .send('shell_setAppVisibility', appId, state); + } +} diff --git a/js/src/shared/config/dappsViews.json b/js/src/shared/config/dappsViews.json index 53baf1dac..8ed3106d4 100644 --- a/js/src/shared/config/dappsViews.json +++ b/js/src/shared/config/dappsViews.json @@ -62,6 +62,36 @@ "visible": true, "noselect": true }, + { + "id": "dappAccounts", + "url": "dappAccounts", + "src": "DappAccounts", + "name": "Default dapp accounts", + "description": "Allow setting of accounts to use by default for dapps", + "author": "Parity Team ", + "version": "2.0.0", + "visible": true + }, + { + "id": "dappMethods", + "url": "dappMethods", + "src": "DappMethods", + "name": "Dapp Method Permissions", + "description": "Allow setting of dapp permissions on a per method level", + "author": "Parity Team ", + "version": "2.0.0", + "visible": true + }, + { + "id": "dappVisible", + "url": "dappVisible", + "src": "DappVisible", + "name": "Dapp Visibility", + "description": "Allow setting of visible dapps", + "author": "Parity Team ", + "version": "2.0.0", + "visible": true + }, { "id": "develop", "url": "develop", diff --git a/js/src/shell/DappRequests/filteredRequests.js b/js/src/shell/DappRequests/filteredRequests.js index 1f3a3317f..743660428 100644 --- a/js/src/shell/DappRequests/filteredRequests.js +++ b/js/src/shell/DappRequests/filteredRequests.js @@ -15,6 +15,15 @@ // along with Parity. If not, see . export default { + shell: { + methods: [ + 'shell_getApps', + 'shell_getFilteredMethods', + 'shell_getMethodPermissions', + 'shell_setAppVisibility', + 'shell_setMethodPermissions' + ] + }, accountsView: { methods: [ 'parity_accountsInfo', diff --git a/js/src/shell/DappRequests/index.js b/js/src/shell/DappRequests/index.js index a8055c3f0..3aa372f0c 100644 --- a/js/src/shell/DappRequests/index.js +++ b/js/src/shell/DappRequests/index.js @@ -16,8 +16,13 @@ import Store from './store'; -export function setupProviderFilters (provider) { +function setupProviderFilters (provider) { return Store.create(provider); } export default from './dappRequests'; + +export { + Store, + setupProviderFilters +}; diff --git a/js/src/shell/DappRequests/store.js b/js/src/shell/DappRequests/store.js index 8ae42340e..272ed3f1b 100644 --- a/js/src/shell/DappRequests/store.js +++ b/js/src/shell/DappRequests/store.js @@ -14,19 +14,27 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { flatten } from 'lodash'; import { action, computed, observable } from 'mobx'; +import store from 'store'; +import { sha3 } from '@parity/api/util/sha3'; + +import VisibleStore from '../Dapps/dappsStore'; import filteredRequests from './filteredRequests'; -import MethodsStore from '../Dapps/SelectMethods/store'; + +const LS_PERMISSIONS = '_parity::dapps::methods'; let nextQueueId = 0; export default class Store { - @observable methodsStore = MethodsStore.get(); + @observable permissions = {}; @observable requests = []; + @observable tokens = {}; constructor (provider) { this.provider = provider; + this.permissions = store.get(LS_PERMISSIONS) || {}; window.addEventListener('message', this.receiveMessage, false); } @@ -51,20 +59,39 @@ export default class Store { }); } + @action createToken = (appId) => { + const token = sha3(`${appId}:${Date.now()}`); + + this.tokens = Object.assign({}, this.tokens, { + [token]: appId + }); + + return token; + } + @action removeRequest = (_queueId) => { this.requests = this.requests.filter(({ queueId }) => queueId !== _queueId); } @action queueRequest = (request) => { - const appId = this.methodsStore.tokens[request.data.from]; + const appId = this.tokens[request.data.from]; let queueId = ++nextQueueId; this.requests = this.requests.concat([{ appId, queueId, request }]); } + @action addTokenPermission = (method, token) => { + const id = `${method}:${this.tokens[token]}`; + + this.permissions = Object.assign({}, this.permissions, { + [id]: true + }); + this.savePermissions(); + } + @action approveSingleRequest = ({ queueId, request: { data, source } }) => { this.removeRequest(queueId); - this.executeOnProvider(data, source); + this.executeMethodCall(data, source); } @action approveRequest = (queueId, approveAll) => { @@ -74,7 +101,7 @@ export default class Store { const { request: { data: { method, token } } } = queued; this.getFilteredSection(method).methods.forEach((m) => { - this.methodsStore.addTokenPermission(m, token); + this.addTokenPermission(m, token); this.findMatchingRequests(m, token).forEach(this.approveSingleRequest); }); } else { @@ -95,6 +122,31 @@ export default class Store { }, '*'); } + @action setPermissions = (_permissions) => { + const permissions = {}; + + Object.keys(_permissions).forEach((id) => { + permissions[id] = !!_permissions[id]; + }); + + this.permissions = Object.assign({}, this.permissions, permissions); + this.savePermissions(); + + return true; + } + + hasTokenPermission = (method, token) => { + return this.hasAppPermission(method, this.tokens[token]); + } + + hasAppPermission = (method, appId) => { + return this.permissions[`${method}:${appId}`] || false; + } + + savePermissions = () => { + store.set(LS_PERMISSIONS, this.permissions); + } + findRequest (_queueId) { return this.requests.find(({ queueId }) => queueId === _queueId); } @@ -103,8 +155,8 @@ export default class Store { return this.requests.filter(({ request: { data: { method, token } } }) => method === _method && token === _token); } - executeOnProvider = ({ id, from, method, params, token }, source) => { - this.provider.send(method, params, (error, result) => { + _methodCallbackPost = (id, source, token) => { + return (error, result) => { source.postMessage({ error: error ? error.message @@ -114,7 +166,48 @@ export default class Store { result, token }, '*'); - }); + }; + } + + executeMethodCall = ({ id, from, method, params, token }, source) => { + const visibleStore = VisibleStore.get(); + const callback = this._methodCallbackPost(id, source, token); + + switch (method) { + case 'shell_getApps': + const [displayAll] = params; + + return callback(null, displayAll + ? visibleStore.allApps.slice() + : visibleStore.visibleApps.slice() + ); + + case 'shell_getFilteredMethods': + return callback(null, flatten( + Object + .keys(filteredRequests) + .map((key) => filteredRequests[key].methods) + )); + + case 'shell_getMethodPermissions': + return callback(null, this.permissions); + + case 'shell_setAppVisibility': + const [appId, visibility] = params; + + return callback(null, visibility + ? visibleStore.showApp(appId) + : visibleStore.hideApp(appId) + ); + + case 'shell_setMethodPermissions': + const [permissions] = params; + + return callback(null, this.setPermissions(permissions)); + + default: + return this.provider.send(method, params, callback); + } } getFilteredSectionName = (method) => { @@ -138,12 +231,12 @@ export default class Store { return; } - if (this.getFilteredSection(method) && !this.methodsStore.hasTokenPermission(method, token)) { + if (this.getFilteredSection(method) && !this.hasTokenPermission(method, token)) { this.queueRequest({ data, origin, source }); return; } - this.executeOnProvider(data, source); + this.executeMethodCall(data, source); } static instance = null; diff --git a/js/src/shell/Dapps/SelectAccounts/selectAccounts.spec.js b/js/src/shell/Dapps/SelectAccounts/selectAccounts.spec.js deleted file mode 100644 index e1d543185..000000000 --- a/js/src/shell/Dapps/SelectAccounts/selectAccounts.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -import { shallow } from 'enzyme'; -import React from 'react'; - -import DappAccounts from './'; - -let component; - -function renderShallow (permissionStore = {}) { - component = shallow( - - ); - - return component; -} - -describe('shell/Dapps/SelectAccounts', () => { - describe('rendering', () => { - it('renders defaults', () => { - expect(renderShallow()).to.be.ok; - }); - - it('does not render the modal with modalOpen = false', () => { - expect( - renderShallow({ modalOpen: false }).find('Portal') - ).to.have.length(0); - }); - - it('does render the modal with modalOpen = true', () => { - expect( - renderShallow({ modalOpen: true, accounts: [] }).find('Portal') - ).to.have.length(1); - }); - }); -}); diff --git a/js/src/shell/Dapps/SelectAccounts/store.spec.js b/js/src/shell/Dapps/SelectAccounts/store.spec.js deleted file mode 100644 index ae0bf64fd..000000000 --- a/js/src/shell/Dapps/SelectAccounts/store.spec.js +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -import sinon from 'sinon'; - -import Store from './store'; - -const ACCOUNTS = { - '123': { address: '123', name: '123', meta: { description: '123' } }, - '456': { address: '456', name: '456', meta: { description: '456' } }, - '789': { address: '789', name: '789', meta: { description: '789' } } -}; -const WHITELIST = ['456', '789']; - -let api; -let store; - -function create () { - api = { - parity: { - getNewDappsAddresses: sinon.stub().resolves(WHITELIST), - getNewDappsDefaultAddress: sinon.stub().resolves(WHITELIST[0]), - setNewDappsAddresses: sinon.stub().resolves(true), - setNewDappsDefaultAddress: sinon.stub().resolves(true) - } - }; - - store = new Store(api); -} - -describe('shell/DappPermissions/store', () => { - beforeEach(() => { - create(); - }); - - describe('constructor', () => { - it('retrieves the whitelist via api', () => { - expect(api.parity.getNewDappsAddresses).to.be.calledOnce; - }); - - it('sets the retrieved whitelist', () => { - expect(store.whitelist.peek()).to.deep.equal(WHITELIST); - }); - }); - - describe('@actions', () => { - beforeEach(() => { - store.openModal(ACCOUNTS); - }); - - describe('openModal', () => { - it('sets the modalOpen status', () => { - expect(store.modalOpen).to.be.true; - }); - - it('sets accounts with checked interfaces', () => { - expect(store.accounts.peek()).to.deep.equal([ - { address: '123', name: '123', description: '123', default: false, checked: false }, - { address: '456', name: '456', description: '456', default: true, checked: true }, - { address: '789', name: '789', description: '789', default: false, checked: true } - ]); - }); - }); - - describe('closeModal', () => { - beforeEach(() => { - store.setDefaultAccount('789'); - store.closeModal(); - }); - - it('calls setNewDappsAddresses', () => { - expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['456', '789']); - }); - - it('calls into setNewDappsDefaultAddress', () => { - expect(api.parity.setNewDappsDefaultAddress).to.have.been.calledWith('789'); - }); - }); - - describe('selectAccount', () => { - beforeEach(() => { - store.selectAccount('123'); - store.selectAccount('789'); - }); - - it('unselects previous selected accounts', () => { - expect(store.accounts.find((account) => account.address === '123').checked).to.be.true; - }); - - it('selects previous unselected accounts', () => { - expect(store.accounts.find((account) => account.address === '789').checked).to.be.false; - }); - - it('sets a new default when default was unselected', () => { - store.selectAccount('456'); - expect(store.accounts.find((account) => account.address === '456').default).to.be.false; - expect(store.accounts.find((account) => account.address === '123').default).to.be.true; - }); - - it('does not deselect the last account', () => { - store.selectAccount('123'); - store.selectAccount('456'); - console.log(store.accounts.map((account) => ({ address: account.address, checked: account.checked }))); - expect(store.accounts.find((account) => account.address === '456').default).to.be.true; - expect(store.accounts.find((account) => account.address === '456').checked).to.be.true; - }); - }); - - describe('setDefaultAccount', () => { - beforeEach(() => { - store.setDefaultAccount('789'); - }); - - it('unselects previous default', () => { - expect(store.accounts.find((account) => account.address === '456').default).to.be.false; - }); - - it('selects new default', () => { - expect(store.accounts.find((account) => account.address === '789').default).to.be.true; - }); - }); - }); -}); diff --git a/js/src/shell/Dapps/SelectMethods/store.js b/js/src/shell/Dapps/SelectMethods/store.js deleted file mode 100644 index b808182e5..000000000 --- a/js/src/shell/Dapps/SelectMethods/store.js +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -import { flatten } from 'lodash'; -import { action, observable } from 'mobx'; -import store from 'store'; - -import { sha3 } from '@parity/api/util/sha3'; - -import filteredRequests from '../../DappRequests/filteredRequests'; - -const LS_PERMISSIONS = '_parity::dapps::methods'; - -export default class Store { - @observable filteredRequests = flatten( - Object.keys(filteredRequests).map((key) => filteredRequests[key].methods) - ); - @observable modalOpen = false; - @observable permissions = {}; - @observable tokens = {}; - - constructor () { - this.permissions = store.get(LS_PERMISSIONS) || {}; - } - - @action closeModal = () => { - this.modalOpen = false; - } - - @action openModal = () => { - this.modalOpen = true; - } - - @action createToken = (appId) => { - const token = sha3(`${appId}:${Date.now()}`); - - this.tokens = Object.assign({}, this.tokens, { - [token]: appId - }); - - return token; - } - - @action addTokenPermission = (method, token) => { - const id = `${method}:${this.tokens[token]}`; - - this.permissions = Object.assign({}, this.permissions, { - [id]: true - }); - this.savePermissions(); - } - - @action toggleAppPermission = (method, appId) => { - const id = `${method}:${appId}`; - - this.permissions = Object.assign({}, this.permissions, { - [id]: !this.permissions[id] - }); - this.savePermissions(); - } - - hasTokenPermission = (method, token) => { - return this.hasAppPermission(method, this.tokens[token]); - } - - hasAppPermission = (method, appId) => { - return this.permissions[`${method}:${appId}`] || false; - } - - savePermissions = () => { - store.set(LS_PERMISSIONS, this.permissions); - } - - static instance = null; - - static get () { - if (!Store.instance) { - Store.instance = new Store(); - } - - return Store.instance; - } -} diff --git a/js/src/shell/Dapps/dapps.js b/js/src/shell/Dapps/dapps.js index 829461b10..6cb246385 100644 --- a/js/src/shell/Dapps/dapps.js +++ b/js/src/shell/Dapps/dapps.js @@ -20,14 +20,7 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { Actionbar, Button, Checkbox, DappCard, Page, SectionList } from '@parity/ui'; -import { LockedIcon, MethodsIcon, VisibleIcon } from '@parity/ui/Icons'; - -import SelectAccounts from './SelectAccounts'; -import PermissionStore from './SelectAccounts/store'; -import DappSelectMethods from './SelectMethods'; -import MethodsStore from './SelectMethods/store'; -import SelectVisible from './SelectVisible'; +import { Checkbox, DappCard, Page, SectionList } from '@parity/ui'; import DappsStore from './dappsStore'; @@ -45,8 +38,6 @@ class Dapps extends Component { }; store = DappsStore.get(this.context.api); - methodsStore = MethodsStore.get(); - permissionStore = new PermissionStore(this.context.api); componentWillMount () { this.store.loadAllApps(); @@ -82,64 +73,19 @@ class Dapps extends Component { } return ( -
- - - - - } - buttons={ [ -
+ + } + > +
{ this.renderList(this.store.visibleViews) }
+
{ this.renderList(this.store.visibleLocal) }
+
{ this.renderList(this.store.visibleBuiltin) }
+
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
+
); } diff --git a/js/src/shell/Dapps/dappsStore.js b/js/src/shell/Dapps/dappsStore.js index 0a672efde..d95222b45 100644 --- a/js/src/shell/Dapps/dappsStore.js +++ b/js/src/shell/Dapps/dappsStore.js @@ -57,6 +57,10 @@ export default class DappsStore extends EventEmitter { return instance; } + @computed get allApps () { + return this.apps; + } + @computed get sortedBuiltin () { return this.apps.filter((app) => app.type === 'builtin'); } diff --git a/js/src/shell/index.js b/js/src/shell/index.js index bd438ace2..aa8ad95a1 100644 --- a/js/src/shell/index.js +++ b/js/src/shell/index.js @@ -41,8 +41,7 @@ import SecureApi from '~/secureApi'; import Application from './Application'; import Dapp from './Dapp'; -import { setupProviderFilters } from './DappRequests'; -import DappMethodsStore from './Dapps/SelectMethods/store'; +import { setupProviderFilters, Store as DappRequestsStore } from './DappRequests'; import Dapps from './Dapps'; injectTapEventPlugin(); @@ -77,7 +76,7 @@ const dapps = [].concat(viewsDapps, builtinDapps); const dappsHistory = HistoryStore.get('dapps'); function onEnterDapp ({ params: { id } }) { - const token = DappMethodsStore.get().createToken(id); + const token = DappRequestsStore.get().createToken(id); window.ethereum = new Api.Provider.PostMessage(token, window); diff --git a/js/src/shell/Dapps/SelectAccounts/index.js b/js/src/views/DappAccounts/api.js similarity index 83% rename from js/src/shell/Dapps/SelectAccounts/index.js rename to js/src/views/DappAccounts/api.js index 27b23b404..6ad6cd0fe 100644 --- a/js/src/shell/Dapps/SelectAccounts/index.js +++ b/js/src/views/DappAccounts/api.js @@ -14,4 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './selectAccounts'; +import Api from '@parity/api'; + +const ethereumProvider = window.ethereum || window.parent.ethereum; + +export default new Api(ethereumProvider); diff --git a/js/src/shell/Dapps/SelectAccounts/selectAccounts.js b/js/src/views/DappAccounts/dappAccounts.js similarity index 71% rename from js/src/shell/Dapps/SelectAccounts/selectAccounts.js rename to js/src/views/DappAccounts/dappAccounts.js index ba0c4e765..37eaf7129 100644 --- a/js/src/shell/Dapps/SelectAccounts/selectAccounts.js +++ b/js/src/views/DappAccounts/dappAccounts.js @@ -18,25 +18,21 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { AccountCard, Portal, SelectionList } from '@parity/ui'; +import { AccountCard, Page, SelectionList } from '@parity/ui'; + +import Store from './store'; @observer -export default class SelectAccounts extends Component { - static propTypes = { - permissionStore: PropTypes.object.isRequired +export default class DappAccounts extends Component { + static contextTypes = { + api: PropTypes.object.isRequired }; + store = new Store(this.context.api); + render () { - const { permissionStore } = this.props; - - if (!permissionStore.modalOpen) { - return null; - } - return ( - - + ); } onMakeDefault = (account) => { - this.props.permissionStore.setDefaultAccount(account.address); + this.store.setDefaultAccount(account.address); } onSelect = (account) => { - this.props.permissionStore.selectAccount(account.address); + this.store.selectAccount(account.address); } renderAccount = (account) => { diff --git a/js/src/shell/Dapps/SelectVisible/selectVisible.spec.js b/js/src/views/DappAccounts/index.js similarity index 52% rename from js/src/shell/Dapps/SelectVisible/selectVisible.spec.js rename to js/src/views/DappAccounts/index.js index aad3fb595..39fdb1972 100644 --- a/js/src/shell/Dapps/SelectVisible/selectVisible.spec.js +++ b/js/src/views/DappAccounts/index.js @@ -14,33 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { shallow } from 'enzyme'; +import ReactDOM from 'react-dom'; import React from 'react'; +import { Route, Router, hashHistory } from 'react-router'; -import DappsVisible from './'; +import injectTapEventPlugin from 'react-tap-event-plugin'; +injectTapEventPlugin(); -function renderShallow (store = {}) { - return shallow( - - ); -} +import { initStore } from '@parity/shared/redux'; +import ContextProvider from '@parity/ui/ContextProvider'; -describe('shell/Dapps/SelectVisible', () => { - describe('rendering', () => { - it('renders defaults', () => { - expect(renderShallow()).to.be.ok; - }); +import api from './api'; +import DappAccounts from './dappAccounts'; - it('does not render the modal with modalOpen = false', () => { - expect( - renderShallow({ modalOpen: false }).find('Portal') - ).to.have.length(0); - }); +const store = initStore(api, hashHistory); - it('does render the modal with modalOpen = true', () => { - expect( - renderShallow({ modalOpen: true }).find('Portal') - ).to.have.length(1); - }); - }); -}); +ReactDOM.render( + + + + + , + document.querySelector('#container') +); diff --git a/js/src/views/DappAccounts/package.json b/js/src/views/DappAccounts/package.json new file mode 100644 index 000000000..72c5b44ab --- /dev/null +++ b/js/src/views/DappAccounts/package.json @@ -0,0 +1,19 @@ +{ + "name": "@parity/view-dapp-accounts", + "description": "Parity default accounts selection", + "version": "0.0.0", + "main": "index.js", + "author": "Parity Team ", + "maintainers": [], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [], + "scripts": {}, + "devDependencies": {}, + "dependencies": {}, + "peerDependencies": {} +} diff --git a/js/src/shell/Dapps/SelectAccounts/store.js b/js/src/views/DappAccounts/store.js similarity index 70% rename from js/src/shell/Dapps/SelectAccounts/store.js rename to js/src/views/DappAccounts/store.js index 0476879a5..c801a652d 100644 --- a/js/src/shell/Dapps/SelectAccounts/store.js +++ b/js/src/views/DappAccounts/store.js @@ -25,40 +25,54 @@ export default class Store { constructor (api) { this._api = api; - this.loadWhitelist(); + this.load(); } - @action closeModal = () => { - transaction(() => { - const checkedAccounts = this.accounts.filter((account) => account.checked); - const defaultAddress = (this.accounts.find((account) => account.default) || {}).address; - const addresses = checkedAccounts.length === this.accounts.length - ? null - : checkedAccounts.map((account) => account.address); + save = () => { + const checkedAccounts = this.accounts.filter((account) => account.checked); + const defaultAddress = (this.accounts.find((account) => account.default) || {}).address; + const addresses = checkedAccounts.length === this.accounts.length + ? null + : checkedAccounts.map((account) => account.address); - this.modalOpen = false; - this.updateWhitelist(addresses, defaultAddress); - }); + this.updateWhitelist(addresses, defaultAddress); } - @action openModal = (accounts) => { + // FIXME: Hardware accounts are not showing up here + @action setAccounts = (accounts) => { transaction(() => { this.accounts = Object - .values(accounts) - .map((account, index) => { + .keys(accounts) + .filter((address) => { + const account = accounts[address]; + + if (account.uuid) { + return true; + } else if (account.meta.hardware) { + account.hardware = true; + return true; + } else if (account.meta.external) { + account.external = true; + return true; + } + + return false; + }) + .map((address, index) => { + const account = accounts[address]; + return { - address: account.address, + address, checked: this.whitelist - ? this.whitelist.includes(account.address) + ? this.whitelist.includes(address) : true, default: this.whitelistDefault - ? this.whitelistDefault === account.address + ? this.whitelistDefault === address : index === 0, description: account.meta.description, name: account.name }; }); - this.modalOpen = true; }); } @@ -103,17 +117,19 @@ export default class Store { }); } - loadWhitelist () { + load () { return Promise .all([ + this._api.parity.allAccountsInfo(), this._api.parity.getNewDappsAddresses(), this._api.parity.getNewDappsDefaultAddress() ]) - .then(([whitelist, whitelistDefault]) => { + .then(([accounts, whitelist, whitelistDefault]) => { this.setWhitelist(whitelist, whitelistDefault); + this.setAccounts(accounts); }) .catch((error) => { - console.warn('loadWhitelist', error); + console.warn('load', error); }); } diff --git a/js/src/shell/Dapps/SelectMethods/MethodCheck/index.js b/js/src/views/DappMethods/MethodCheck/index.js similarity index 100% rename from js/src/shell/Dapps/SelectMethods/MethodCheck/index.js rename to js/src/views/DappMethods/MethodCheck/index.js diff --git a/js/src/shell/Dapps/SelectMethods/MethodCheck/methodCheck.js b/js/src/views/DappMethods/MethodCheck/methodCheck.js similarity index 100% rename from js/src/shell/Dapps/SelectMethods/MethodCheck/methodCheck.js rename to js/src/views/DappMethods/MethodCheck/methodCheck.js diff --git a/js/src/shell/Dapps/SelectVisible/index.js b/js/src/views/DappMethods/api.js similarity index 83% rename from js/src/shell/Dapps/SelectVisible/index.js rename to js/src/views/DappMethods/api.js index fd2e5f1ce..6ad6cd0fe 100644 --- a/js/src/shell/Dapps/SelectVisible/index.js +++ b/js/src/views/DappMethods/api.js @@ -14,4 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './selectVisible'; +import Api from '@parity/api'; + +const ethereumProvider = window.ethereum || window.parent.ethereum; + +export default new Api(ethereumProvider); diff --git a/js/src/shell/Dapps/SelectMethods/selectMethods.css b/js/src/views/DappMethods/dappMethods.css similarity index 99% rename from js/src/shell/Dapps/SelectMethods/selectMethods.css rename to js/src/views/DappMethods/dappMethods.css index e8d13ae03..36afd036f 100644 --- a/js/src/shell/Dapps/SelectMethods/selectMethods.css +++ b/js/src/views/DappMethods/dappMethods.css @@ -17,7 +17,7 @@ $border: 1px solid #ccc; -.modal { +.body { td { border-bottom: $border; border-right: $border; diff --git a/js/src/shell/Dapps/SelectMethods/selectMethods.js b/js/src/views/DappMethods/dappMethods.js similarity index 68% rename from js/src/shell/Dapps/SelectMethods/selectMethods.js rename to js/src/views/DappMethods/dappMethods.js index 0eab312a7..ca9074337 100644 --- a/js/src/shell/Dapps/SelectMethods/selectMethods.js +++ b/js/src/views/DappMethods/dappMethods.js @@ -18,30 +18,25 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Portal } from '@parity/ui'; +import { Page } from '@parity/ui'; + +import Store from './store'; import MethodCheck from './MethodCheck'; -import styles from './selectMethods.css'; +import styles from './dappMethods.css'; @observer export default class SelectMethods extends Component { - static propTypes = { - methodsStore: PropTypes.object.isRequired, - visibleStore: PropTypes.object.isRequired + static contextTypes = { + api: PropTypes.object.isRequired }; + store = new Store(this.context.api); + render () { - const { methodsStore, visibleStore } = this.props; - - if (!methodsStore.modalOpen) { - return null; - } - return ( -   { - methodsStore.filteredRequests.map((method, requestIndex) => ( - + this.store.methods.map((method, methodIndex) => ( +
{ method }
@@ -66,20 +61,20 @@ export default class SelectMethods extends Component { { - visibleStore.visibleApps.map(({ id, name }, dappIndex) => ( + this.store.apps.map(({ id, name }, dappIndex) => ( { name } { - methodsStore.filteredRequests.map((method, requestIndex) => ( + this.store.methods.map((method, methodIndex) => ( )) @@ -89,7 +84,7 @@ export default class SelectMethods extends Component { } -
+ ); } } diff --git a/js/src/views/DappMethods/index.js b/js/src/views/DappMethods/index.js new file mode 100644 index 000000000..dee1b60b6 --- /dev/null +++ b/js/src/views/DappMethods/index.js @@ -0,0 +1,42 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import ReactDOM from 'react-dom'; +import React from 'react'; +import { Route, Router, hashHistory } from 'react-router'; + +import injectTapEventPlugin from 'react-tap-event-plugin'; +injectTapEventPlugin(); + +import ContractInstances from '@parity/shared/contracts'; +import { initStore } from '@parity/shared/redux'; +import ContextProvider from '@parity/ui/ContextProvider'; + +import api from './api'; +import DappMethods from './dappMethods'; + +ContractInstances.get(api); + +const store = initStore(api, hashHistory); + +ReactDOM.render( + + + + + , + document.querySelector('#container') +); diff --git a/js/src/views/DappMethods/package.json b/js/src/views/DappMethods/package.json new file mode 100644 index 000000000..7116205b8 --- /dev/null +++ b/js/src/views/DappMethods/package.json @@ -0,0 +1,19 @@ +{ + "name": "@parity/view-dapp-methods", + "description": "Parity default dapp method selection", + "version": "0.0.0", + "main": "index.js", + "author": "Parity Team ", + "maintainers": [], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [], + "scripts": {}, + "devDependencies": {}, + "dependencies": {}, + "peerDependencies": {} +} diff --git a/js/src/views/DappMethods/store.js b/js/src/views/DappMethods/store.js new file mode 100644 index 000000000..c74b4d869 --- /dev/null +++ b/js/src/views/DappMethods/store.js @@ -0,0 +1,73 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { action, observable } from 'mobx'; + +export default class Store { + @observable apps= []; + @observable methods = []; + @observable permissions = {}; + + constructor (api) { + this._api = api; + + this.loadInitialise(); + } + + @action setApps = (apps) => { + this.apps = apps; + } + + @action setMethods = (methods) => { + this.methods = methods; + } + + @action setPermissions = (permissions) => { + this.permissions = permissions; + } + + @action toggleAppPermission = (method, appId) => { + const id = `${method}:${appId}`; + + this.permissions = Object.assign({}, this.permissions, { + [id]: !this.permissions[id] + }); + + this.savePermissions(); + } + + hasAppPermission = (method, appId) => { + return this.permissions[`${method}:${appId}`] || false; + } + + loadInitialise = () => { + return Promise + .all([ + this._api.shell.getApps(false), + this._api.shell.getFilteredMethods(), + this._api.shell.getMethodPermissions() + ]) + .then(([apps, methods, permissions]) => { + this.setApps(apps); + this.setMethods(methods); + this.setPermissions(permissions); + }); + } + + savePermissions = () => { + this._api.shell.setMethodPermissions(this.permissions); + } +} diff --git a/js/src/views/DappVisible/api.js b/js/src/views/DappVisible/api.js new file mode 100644 index 000000000..6ad6cd0fe --- /dev/null +++ b/js/src/views/DappVisible/api.js @@ -0,0 +1,21 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import Api from '@parity/api'; + +const ethereumProvider = window.ethereum || window.parent.ethereum; + +export default new Api(ethereumProvider); diff --git a/js/src/shell/Dapps/SelectVisible/selectVisible.css b/js/src/views/DappVisible/dappVisible.css similarity index 100% rename from js/src/shell/Dapps/SelectVisible/selectVisible.css rename to js/src/views/DappVisible/dappVisible.css diff --git a/js/src/shell/Dapps/SelectVisible/selectVisible.js b/js/src/views/DappVisible/dappVisible.js similarity index 80% rename from js/src/shell/Dapps/SelectVisible/selectVisible.js rename to js/src/views/DappVisible/dappVisible.js index da8bba1c5..e84297ab7 100644 --- a/js/src/shell/Dapps/SelectVisible/selectVisible.js +++ b/js/src/views/DappVisible/dappVisible.js @@ -18,28 +18,22 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { DappCard, Portal, SelectionList } from '@parity/ui'; +import { DappCard, Page, SelectionList } from '@parity/ui'; -import styles from './selectVisible.css'; +import Store from './store'; +import styles from './dappVisible.css'; @observer -export default class SelectVisible extends Component { - static propTypes = { - store: PropTypes.object.isRequired +export default class DappVisible extends Component { + static contextTypes = { + api: PropTypes.object.isRequired }; + store = new Store(this.context.api); + render () { - const { store } = this.props; - - if (!store.modalOpen) { - return null; - } - return ( -
{ - this.renderList(store.sortedLocal, store.displayApps, + this.renderList(this.store.sortedLocal, this.store.displayApps, ) } - + ); } @@ -120,18 +114,14 @@ export default class SelectVisible extends Component { } isVisible = (app) => { - const { store } = this.props; - - return store.displayApps[app.id].visible; + return (this.store.displayApps[app.id] && this.store.displayApps[app.id].visible) || false; } onSelect = (app) => { - const { store } = this.props; - if (this.isVisible(app)) { - store.hideApp(app.id); + this.store.hideApp(app.id); } else { - store.showApp(app.id); + this.store.showApp(app.id); } } } diff --git a/js/src/views/DappVisible/index.js b/js/src/views/DappVisible/index.js new file mode 100644 index 000000000..f90151bfa --- /dev/null +++ b/js/src/views/DappVisible/index.js @@ -0,0 +1,39 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import ReactDOM from 'react-dom'; +import React from 'react'; +import { Route, Router, hashHistory } from 'react-router'; + +import injectTapEventPlugin from 'react-tap-event-plugin'; +injectTapEventPlugin(); + +import { initStore } from '@parity/shared/redux'; +import ContextProvider from '@parity/ui/ContextProvider'; + +import api from './api'; +import DappVisible from './dappVisible'; + +const store = initStore(api, hashHistory); + +ReactDOM.render( + + + + + , + document.querySelector('#container') +); diff --git a/js/src/views/DappVisible/package.json b/js/src/views/DappVisible/package.json new file mode 100644 index 000000000..b0856ba34 --- /dev/null +++ b/js/src/views/DappVisible/package.json @@ -0,0 +1,19 @@ +{ + "name": "@parity/view-dapp-visible", + "description": "Parity default dapp visibility selection", + "version": "0.0.0", + "main": "index.js", + "author": "Parity Team ", + "maintainers": [], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [], + "scripts": {}, + "devDependencies": {}, + "dependencies": {}, + "peerDependencies": {} +} diff --git a/js/src/views/DappVisible/store.js b/js/src/views/DappVisible/store.js new file mode 100644 index 000000000..0c4b2b9ef --- /dev/null +++ b/js/src/views/DappVisible/store.js @@ -0,0 +1,101 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { action, computed, observable } from 'mobx'; + +export default class DappsStore { + @observable apps = []; + @observable displayApps = {}; + + _api = null; + + constructor (api) { + this._api = api; + + this.loadApps(); + } + + @computed get sortedBuiltin () { + return this.apps.filter((app) => app.type === 'builtin'); + } + + @computed get sortedLocal () { + return this.apps.filter((app) => app.type === 'local'); + } + + @computed get sortedNetwork () { + return this.apps.filter((app) => app.type === 'network'); + } + + @computed get visibleApps () { + return this.apps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible); + } + + @computed get visibleBuiltin () { + return this.visibleApps.filter((app) => !app.noselect && app.type === 'builtin'); + } + + @computed get visibleLocal () { + return this.visibleApps.filter((app) => app.type === 'local'); + } + + @computed get visibleNetwork () { + return this.visibleApps.filter((app) => app.type === 'network'); + } + + @computed get visibleViews () { + return this.visibleApps.filter((app) => !app.noselect && app.type === 'view'); + } + + @action setApps = (apps) => { + this.apps = apps; + } + + @action setDisplayApps = (displayApps) => { + this.displayApps = Object.assign({}, this.displayApps, displayApps); + }; + + @action hideApp = (id) => { + this.setDisplayApps({ [id]: { visible: false } }); + this._api.shell.setAppVisibility(id, false); + } + + @action showApp = (id) => { + this.setDisplayApps({ [id]: { visible: true } }); + this._api.shell.setAppVisibility(id, true); + } + + getAppById = (id) => { + return this.apps.find((app) => app.id === id); + } + + loadApps () { + return Promise + .all([ + this._api.shell.getApps(true), + this._api.shell.getApps(false) + ]) + .then(([all, displayed]) => { + this.setDisplayApps( + displayed.reduce((result, { id }) => { + result[id] = { visible: true }; + return result; + }, {}) + ); + this.setApps(all); + }); + } +}