UI server refactoring (#5580)

* Full API in Authenticated WS server.

* Replacing UI server with Hyper.

* Solving CLI, RPCs and tests.

* Porting signer tests.

* Fixing origin recognition for dapps/rpc.

* Fixing tests. Adding parity-rpc-client to test.

* Dapps exposed as RPC method.

* JS code to support new connection scheme.

* Fixing dapps tests.

* Updating allowed origins/hosts to support web3.site.

* Fixing tests, fixing UI.

* Fixing tests.

* Removing invalid tests.

* Fixing merge.

* 404 fallback for UI

* Improve ContentFetcher constructor readability.

* Naming.

* Update .gitlab-ci.yml

fix CI lint error

* Fixing tests and linting issues.

* Fixing new tests.

* UI hosts.

* Submodules fix.
This commit is contained in:
Tomasz Drwięga
2017-05-24 12:24:07 +02:00
committed by Arkadiy Paronyan
parent 7499efecf6
commit cbcc369a2d
91 changed files with 2171 additions and 2591 deletions

View File

@@ -90,15 +90,14 @@ export default class Parity {
.execute('parity_consensusCapability');
}
dappsPort () {
dappsList () {
return this._transport
.execute('parity_dappsPort')
.then(outNumber);
.execute('parity_dappsList');
}
dappsInterface () {
dappsUrl () {
return this._transport
.execute('parity_dappsInterface');
.execute('parity_dappsUrl');
}
decryptMessage (address, data) {
@@ -530,12 +529,6 @@ export default class Parity {
.execute('parity_setVaultMeta', vaultName, JSON.stringify(meta));
}
signerPort () {
return this._transport
.execute('parity_signerPort')
.then(outNumber);
}
signMessage (address, password, messageHash) {
return this._transport
.execute('parity_signMessage', inAddress(address), password, inHex(messageHash));
@@ -567,4 +560,9 @@ export default class Parity {
return this._transport
.execute('parity_versionInfo');
}
wsUrl () {
return this._transport
.execute('parity_wsUrl');
}
}

File diff suppressed because one or more lines are too long

View File

@@ -16,8 +16,6 @@
import React from 'react';
import { parityNode } from '../../../environment';
const styles = {
padding: '.5em',
border: '1px solid #777'
@@ -34,7 +32,7 @@ export default (address) => {
return (
<img
src={ `${parityNode}/api/content/${address.replace(/^0x/, '')}` }
src={ `/api/content/${address.replace(/^0x/, '')}` }
alt={ address }
style={ styles }
/>

View File

@@ -30,7 +30,6 @@ import styles from './token.css';
import { metaDataKeys } from '../../constants';
import { api } from '../../parity';
import { parityNode } from '../../../../environment';
export default class Token extends Component {
static propTypes = {
@@ -312,7 +311,7 @@ export default class Token extends Component {
</span> meta-data:
</p>
<div className={ styles['meta-image'] }>
<img src={ `${parityNode}/api/content/${imageHash}/` } />
<img src={ `/api/content/${imageHash}/` } />
</div>
</div>
);

View File

@@ -55,6 +55,9 @@ class FakeTransport {
return Promise.reject('not connected');
}
addMiddleware () {
}
on () {
}
}

View File

@@ -19,14 +19,4 @@
import './tests';
const parityNode = (
process.env.PARITY_URL && `http://${process.env.PARITY_URL}`
) || (
process.env.NODE_ENV === 'production'
? 'http://127.0.0.1:8545'
: ''
);
export {
parityNode
};
export {};

View File

@@ -53,8 +53,7 @@ if (process.env.NODE_ENV === 'development') {
}
const AUTH_HASH = '#/auth?';
const parityUrl = process.env.PARITY_URL || window.location.host;
const urlScheme = window.location.href.match(/^https/) ? 'wss://' : 'ws://';
const parityUrl = process.env.PARITY_URL || '127.0.0.1:8546';
let token = null;
@@ -62,7 +61,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
}
const api = new SecureApi(`${urlScheme}${parityUrl}`, token);
const api = new SecureApi(parityUrl, token);
patchApi(api);
loadSender(api);

View File

@@ -143,25 +143,34 @@ export default {
}
},
dappsPort: {
section: SECTION_NODE,
desc: 'Returns the port the dapps are running on, error if not enabled.',
dappsList: {
subdoc: SUBDOC_SET,
desc: 'Returns a list of available local dapps.',
params: [],
returns: {
type: Quantity,
desc: 'The port number',
example: 8080
type: Array,
desc: 'The list of dapps',
example: [
{
author: 'Parity Technologies Ltd',
description: 'A skeleton dapp',
iconUrl: 'title.png',
id: 'skeleton',
name: 'Skeleton',
version: '0.1'
}
]
}
},
dappsInterface: {
dappsUrl: {
section: SECTION_NODE,
desc: 'Returns the interface the dapps are running on, error if not enabled.',
desc: 'Returns the hostname and the port of dapps/rpc server, error if not enabled.',
params: [],
returns: {
type: String,
desc: 'The interface',
example: '127.0.0.1'
desc: 'The hostname and port number',
example: 'localhost:8545'
}
},
@@ -788,17 +797,6 @@ export default {
}
},
signerPort: {
section: SECTION_NODE,
desc: 'Returns the port the signer is running on, error if not enabled',
params: [],
returns: {
type: Quantity,
desc: 'The port number',
example: 8180
}
},
transactionsLimit: {
section: SECTION_MINING,
desc: 'Changes limit for transactions in queue.',
@@ -1916,6 +1914,17 @@ export default {
}
},
wsUrl: {
section: SECTION_NODE,
desc: 'Returns the hostname and the port of WebSockets/Signer server, error if not enabled.',
params: [],
returns: {
type: String,
desc: 'The hostname and port number',
example: 'localhost:8546'
}
},
composeTransaction: {
desc: 'Given partial transaction request produces transaction with all fields filled in. Such transaction can be then signed externally.',
params: [
@@ -1997,4 +2006,5 @@ export default {
example: 'QmSbFjqjd6nFwNHqsBCC7SK8GShGcayLUEtysJjNGhZAnC'
}
}
};

View File

@@ -27,21 +27,28 @@ export default class SecureApi extends Api {
_needsToken = false;
_tokens = [];
_dappsInterface = null;
_dappsPort = 8545;
_signerPort = 8180;
_dappsUrl = null;
_wsUrl = null;
static getTransport (url, sysuiToken) {
return new Api.Transport.Ws(url, sysuiToken, false);
static getTransport (url, sysuiToken, protocol) {
const proto = protocol() === 'https:' ? 'wss:' : 'ws:';
return new Api.Transport.Ws(`${proto}//${url}`, sysuiToken, false);
}
constructor (url, nextToken, getTransport = SecureApi.getTransport) {
// Returns a protocol with `:` at the end.
static protocol () {
return window.location.protocol;
}
constructor (url, nextToken, getTransport = SecureApi.getTransport, protocol = SecureApi.protocol) {
const sysuiToken = store.get('sysuiToken');
const transport = getTransport(url, sysuiToken);
const transport = getTransport(url, sysuiToken, protocol);
super(transport);
this._url = url;
this._wsUrl = url;
this.protocol = protocol;
// Try tokens from localStorage, from hash and 'initial'
this._tokens = uniq([sysuiToken, nextToken, 'initial'])
.filter((token) => token)
@@ -53,12 +60,30 @@ export default class SecureApi extends Api {
this.connect();
}
get _dappsAddress () {
if (!this._dappsUrl) {
return {
host: null,
port: 8545
};
}
const [host, port] = this._dappsUrl.split(':');
return {
host,
port: parseInt(port, 10)
};
}
get dappsPort () {
return this._dappsPort;
return this._dappsAddress.port;
}
get dappsUrl () {
return `http://${this.hostname}:${this.dappsPort}`;
const { port } = this._dappsAddress;
return `${this.protocol()}//${this.hostname}:${port}`;
}
get hostname () {
@@ -66,15 +91,13 @@ export default class SecureApi extends Api {
return 'dapps.parity';
}
if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') {
const { host } = this._dappsAddress;
if (!host || host === '0.0.0.0') {
return window.location.hostname;
}
return this._dappsInterface;
}
get signerPort () {
return this._signerPort;
return host;
}
get isConnecting () {
@@ -98,18 +121,18 @@ export default class SecureApi extends Api {
* (`signerPort`, `dappsInterface`, `dappsPort`, ...)
*/
configure (configuration) {
const { dappsInterface, dappsPort, signerPort } = configuration;
const { dappsInterface, dappsPort, signerPort, wsPort } = configuration;
if (dappsInterface) {
this._dappsInterface = dappsInterface;
this._dappsUrl = `${dappsInterface}:${this._dappsAddress.port}`;
}
if (dappsPort) {
this._dappsPort = dappsPort;
this._dappsUrl = `${this.hostname}:${dappsPort}`;
}
if (signerPort) {
this._signerPort = signerPort;
if (signerPort || wsPort) {
this._wsUrl = `${this.hostname}:${signerPort || wsPort}`;
}
}
@@ -166,9 +189,7 @@ export default class SecureApi extends Api {
* otherwise (HEAD request to the Node)
*/
isNodeUp () {
const url = this._url.replace(/wss?/, 'http');
return fetch(url, { method: 'HEAD' })
return fetch(`${this.protocol()}//${this._wsUrl}`, { method: 'HEAD', mode: 'no-cors' })
.then(
(r) => r.status === 200,
() => false
@@ -297,14 +318,12 @@ export default class SecureApi extends Api {
_fetchSettings () {
return Promise
.all([
this.parity.dappsPort(),
this.parity.dappsInterface(),
this.parity.signerPort()
this.parity.dappsUrl(),
this.parity.wsUrl()
])
.then(([dappsPort, dappsInterface, signerPort]) => {
this._dappsPort = dappsPort.toNumber();
this._dappsInterface = dappsInterface;
this._signerPort = signerPort.toNumber();
.then(([dappsUrl, wsUrl]) => {
this._dappsUrl = dappsUrl;
this._wsUrl = dappsUrl;
});
}

View File

@@ -25,21 +25,6 @@ import builtinJson from '~/views/Dapps/builtin.json';
const builtinApps = builtinJson.filter((app) => app.id);
function getHost (api) {
const host = process.env.DAPPS_URL ||
(
process.env.NODE_ENV === 'production'
? api.dappsUrl
: ''
);
if (host === '/') {
return '';
}
return host;
}
export function subscribeToChanges (api, dappReg, callback) {
return dappReg
.getContract()
@@ -105,12 +90,7 @@ export function fetchBuiltinApps () {
}
export function fetchLocalApps (api) {
return fetch(`${getHost(api)}/api/apps`)
.then((response) => {
return response.ok
? response.json()
: [];
})
return api.parity.dappsList()
.then((apps) => {
return apps
.map((app) => {
@@ -195,7 +175,7 @@ export function fetchManifest (api, manifestHash) {
}
return fetch(
`${getHost(api)}/api/content/${manifestHash}/`,
`/api/content/${manifestHash}/`,
{ redirect: 'follow', mode: 'cors' }
)
.then((response) => {

View File

@@ -26,17 +26,11 @@ const APPID_DAPPREG = '0x7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5
const APPID_GHH = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75';
const APPID_LOCALTX = '0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d';
const APPID_TOKENDEPLOY = '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f';
const FETCH_OK = {
ok: true,
status: 200
};
let globalContractsGet;
let globalFetch;
function stubGlobals () {
globalContractsGet = Contracts.get;
globalFetch = global.fetch;
Contracts.get = () => {
return {
@@ -50,31 +44,21 @@ function stubGlobals () {
}
};
};
global.fetch = (url) => {
switch (url) {
case '/api/apps':
return Promise.resolve(Object.assign({}, FETCH_OK, {
json: sinon.stub().resolves([]) // TODO: Local stubs in here
}));
default:
console.log('Unknown fetch stub endpoint', url);
return Promise.reject();
}
};
}
function restoreGlobals () {
Contracts.get = globalContractsGet;
global.fetch = globalFetch;
}
let api;
let store;
function create () {
api = {};
api = {
parity: {
dappsList: () => Promise.resolve([])
}
};
store = new Store(api);
return store;

View File

@@ -34,8 +34,8 @@ let store;
function createApi () {
api = {
dappsPort: 8080,
dappsUrl: 'http://home.web3.site:8080',
dappsPort: 8545,
dappsUrl: 'http://home.web3.site:8545',
parity: {
listRecentDapps: sinon.stub().resolves(TEST_HISTORY)
},
@@ -159,7 +159,7 @@ describe('views/Web/Store', () => {
it('encodes current', () => {
store.setCurrentUrl(TEST_URL1);
expect(store.encodedPath).to.match(
/http:\/\/home\.web3\.site:8080\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/
/http:\/\/home\.web3\.site:8545\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/
);
});
});
@@ -167,7 +167,7 @@ describe('views/Web/Store', () => {
it('encodes current', () => {
store.setCurrentUrl(TEST_URL1);
expect(store.encodedUrl).to.match(
/^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8080\?t=[0-9]*$/
/^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8545\?t=[0-9]*$/
);
});
});

View File

@@ -15,26 +15,21 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
// test only
/**
* Run `DAPPS_URL="/" PARITY_URL="127.0.0.1:8180" NODE_ENV="production" npm run build`
* Run `DAPPS_URL="/" PARITY_URL="127.0.0.1:8546" NODE_ENV="production" npm run build`
* to build the project ; use this server to test that the minifed
* version is working (this is a simple proxy server)
*/
var express = require('express');
var proxy = require('http-proxy-middleware');
var Shared = require('./shared');
var app = express();
var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true });
Shared.addProxies(app);
app.use(express.static('.build'));
app.use(wsProxy);
var server = app.listen(process.env.PORT || 3000, function () {
console.log('Listening on port', server.address().port);
});
server.on('upgrade', wsProxy.upgrade);

View File

@@ -22,7 +22,6 @@ const webpackHotMiddleware = require('webpack-hot-middleware');
const http = require('http');
const express = require('express');
const ProgressBar = require('progress');
const proxy = require('http-proxy-middleware');
const webpackConfig = require('./app');
const Shared = require('./shared');
@@ -84,18 +83,13 @@ app.use(webpackDevMiddleware(compiler, {
}
}));
var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true });
// Add the dev proxies in the express App
Shared.addProxies(app);
app.use(express.static(webpackConfig.output.path));
app.use(wsProxy);
const server = http.createServer(app);
server.listen(process.env.PORT || 3000, function () {
console.log('Listening on port', server.address().port);
progressBar = new ProgressBar('[:bar] :percent :etas', { total: 50 });
});
server.on('upgrade', wsProxy.upgrade);

View File

@@ -162,16 +162,8 @@ function getDappsEntry () {
function addProxies (app) {
const proxy = require('http-proxy-middleware');
app.use(proxy((pathname, req) => {
return pathname === '/' && req.method === 'HEAD';
}, {
target: 'http://127.0.0.1:8180',
changeOrigin: true,
autoRewrite: true
}));
app.use('/api', proxy({
target: 'http://127.0.0.1:8545',
target: 'http://127.0.0.1:8180',
changeOrigin: true,
autoRewrite: true
}));