diff --git a/js/src/index.parity.js b/js/src/index.parity.js
index e9059a0ea..82459e826 100644
--- a/js/src/index.parity.js
+++ b/js/src/index.parity.js
@@ -21,7 +21,6 @@ import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { IndexRoute, Redirect, Route, Router, hashHistory } from 'react-router';
-import qs from 'querystring';
import ContractInstances from '@parity/shared/lib/contracts';
import { initStore } from '@parity/shared/lib/redux';
@@ -29,6 +28,9 @@ import ContextProvider from '@parity/ui/lib/ContextProvider';
import '@parity/shared/lib/environment';
+import { redirectLocalhost } from './util/host';
+import { retrieveToken } from './util/token';
+
import Application from './Application';
import Dapp from './Dapp';
import Dapps from './Dapps';
@@ -52,45 +54,45 @@ if (process.env.NODE_ENV === 'development') {
}
*/
-const AUTH_HASH = '#/auth?';
+function renderUI (token) {
+ const api = new SecureApi(window.location.host, token);
-let token = null;
+ api.parity.registryAddress().then((address) => console.log('registryAddress', address)).catch((error) => console.error('registryAddress', error));
-if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
- token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
+ ContractInstances.get(api);
+
+ setupProviderFilters(api.provider);
+
+ const store = initStore(api, hashHistory);
+
+ console.log('UI version', process.env.UI_VERSION);
+
+ ReactDOM.render(
+
+
+
+
+
+
+
+
+
+ ,
+ document.querySelector('#container')
+ );
+
+ // testing, priceTicker gist
+ // injectExternalScript('https://cdn.rawgit.com/jacogr/396fc583e81b9404e21195a48dc862ca/raw/33e5058a4c0028cf9acf4b0662d75298e41ca6fa/priceTicker.js');
+
+ // testing, signer plugins
+ require('@parity/plugin-signer-account');
+ require('@parity/plugin-signer-default');
+ require('@parity/plugin-signer-hardware');
+ require('@parity/plugin-signer-qr');
}
-const api = new SecureApi(window.location.host, token);
+const token = retrieveToken();
-api.parity.registryAddress().then((address) => console.log('registryAddress', address)).catch((error) => console.error('registryAddress', error));
-
-ContractInstances.get(api);
-
-setupProviderFilters(api.provider);
-
-const store = initStore(api, hashHistory);
-
-console.log('UI version', process.env.UI_VERSION);
-
-ReactDOM.render(
-
-
-
-
-
-
-
-
-
- ,
- document.querySelector('#container')
-);
-
-// testing, priceTicker gist
-// injectExternalScript('https://cdn.rawgit.com/jacogr/396fc583e81b9404e21195a48dc862ca/raw/33e5058a4c0028cf9acf4b0662d75298e41ca6fa/priceTicker.js');
-
-// testing, signer plugins
-import '@parity/plugin-signer-account';
-import '@parity/plugin-signer-default';
-import '@parity/plugin-signer-hardware';
-import '@parity/plugin-signer-qr';
+if (!redirectLocalhost(token)) {
+ renderUI(token);
+}
diff --git a/js/src/util/host.js b/js/src/util/host.js
new file mode 100644
index 000000000..09dc7601d
--- /dev/null
+++ b/js/src/util/host.js
@@ -0,0 +1,40 @@
+// 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 function createLocation (token, location = window.location) {
+ const { hash, port, protocol } = location;
+ let query = '';
+
+ if (hash && hash.indexOf('?') !== -1) {
+ // TODO: currently no app uses query-params visible in the shell, this may need adjustment if they do
+ query = hash;
+ } else {
+ query = `${hash || '#/'}${token ? '?token=' : ''}${token || ''}`;
+ }
+
+ return `${protocol}//127.0.0.1:${port}/${query}`;
+}
+
+export function redirectLocalhost (token) {
+ // we don't want localhost, rather we want 127.0.0.1
+ if (window.location.hostname !== 'localhost') {
+ return false;
+ }
+
+ window.location.assign(createLocation(token, window.location));
+
+ return true;
+}
diff --git a/js/src/util/host.spec.js b/js/src/util/host.spec.js
new file mode 100644
index 000000000..baa34eed1
--- /dev/null
+++ b/js/src/util/host.spec.js
@@ -0,0 +1,49 @@
+// 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 { createLocation } from './host';
+
+describe('createLocation', () => {
+ it('only changes the host with no token', () => {
+ expect(
+ createLocation('', { protocol: 'http:', port: 3000, hostname: 'localhost' })
+ ).to.equal('http://127.0.0.1:3000/#/');
+ });
+
+ it('preserves hash when changing the host', () => {
+ expect(
+ createLocation('', { protocol: 'http:', port: 3000, hostname: 'localhost', hash: '#/accounts' })
+ ).to.equal('http://127.0.0.1:3000/#/accounts');
+ });
+
+ it('adds the token when required', () => {
+ expect(
+ createLocation('test', { protocol: 'http:', port: 3000, hostname: 'localhost' })
+ ).to.equal('http://127.0.0.1:3000/#/?token=test');
+ });
+
+ it('preserves hash when token adjusted', () => {
+ expect(
+ createLocation('test', { protocol: 'http:', port: 3000, hostname: 'localhost', hash: '#/accounts' })
+ ).to.equal('http://127.0.0.1:3000/#/accounts?token=test');
+ });
+
+ it('does not override already-passed parameters', () => {
+ expect(
+ createLocation('test', { protocol: 'http:', port: 3000, hostname: 'localhost', hash: '#/accounts?token=abc' })
+ ).to.equal('http://127.0.0.1:3000/#/accounts?token=abc');
+ });
+});
diff --git a/js/src/util/token.js b/js/src/util/token.js
new file mode 100644
index 000000000..cf0683f5a
--- /dev/null
+++ b/js/src/util/token.js
@@ -0,0 +1,54 @@
+// 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 qs from 'querystring';
+import store from 'store';
+
+const TOKEN_QS = 'token=';
+
+function parseTokenQuery (query) {
+ try {
+ return qs.parse(query).token;
+ } catch (error) {
+ return null;
+ }
+}
+
+export function retrieveToken (location = window.location) {
+ const hashIndex = location.hash
+ ? location.hash.indexOf(TOKEN_QS)
+ : -1;
+ const searchIndex = location.search
+ ? location.search.indexOf(TOKEN_QS)
+ : -1;
+
+ let token = null;
+
+ if (hashIndex !== -1) {
+ // extract from hash (e.g. http://127.0.0.1:8180/#/auth?token=...)
+ token = parseTokenQuery(location.hash.substr(hashIndex));
+ } else if (searchIndex !== -1) {
+ // extract from query (e.g. http://127.0.0.1:3000/?token=...)
+ token = parseTokenQuery(location.search);
+ }
+
+ if (!token) {
+ // we don't have a token, attempt from localStorage
+ token = store.get('sysuiToken');
+ }
+
+ return token;
+}
diff --git a/js/src/util/token.spec.js b/js/src/util/token.spec.js
new file mode 100644
index 000000000..06ebe02b6
--- /dev/null
+++ b/js/src/util/token.spec.js
@@ -0,0 +1,55 @@
+// 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 { retrieveToken } from './token';
+
+describe('retrieveToken', () => {
+ it('returns nothing when not found in hash, search or localStorage', () => {
+ expect(retrieveToken()).not.to.be.ok;
+ });
+
+ describe('localStorage', () => {
+ beforeEach(() => {
+ localStorage.setItem('sysuiToken', 'yQls-7TX1-t0Jb-0001');
+ });
+
+ afterEach(() => {
+ localStorage.removeItem('sysuiToken');
+ });
+
+ it('retrieves the token', () => {
+ expect(retrieveToken()).to.equal('yQls-7TX1-t0Jb-0001');
+ });
+ });
+
+ describe('URL', () => {
+ it('retrieves the token from search', () => {
+ expect(
+ retrieveToken({
+ search: 'token=yQls-7TX1-t0Jb-0002'
+ })
+ ).to.equal('yQls-7TX1-t0Jb-0002');
+ });
+
+ it('retrieves the token from hash', () => {
+ expect(
+ retrieveToken({
+ hash: '#/auth?token=yQls-7TX1-t0Jb-0003'
+ })
+ ).to.equal('yQls-7TX1-t0Jb-0003');
+ });
+ });
+});
diff --git a/parity/configuration.rs b/parity/configuration.rs
index 11a869fdc..b912689eb 100644
--- a/parity/configuration.rs
+++ b/parity/configuration.rs
@@ -567,7 +567,7 @@ impl Configuration {
}
fn dapps_config(&self) -> DappsConfiguration {
- let dev_ui = if self.args.flag_ui_no_validation { vec![("localhost".to_owned(), 3000)] } else { vec![] };
+ let dev_ui = if self.args.flag_ui_no_validation { vec![("127.0.0.1".to_owned(), 3000)] } else { vec![] };
let ui_port = self.ui_port();
DappsConfiguration {
@@ -1578,7 +1578,7 @@ mod tests {
port: 8180,
hosts: Some(vec![]),
});
- assert_eq!(conf1.dapps_config().extra_embed_on, vec![("localhost".to_owned(), 3000)]);
+ assert_eq!(conf1.dapps_config().extra_embed_on, vec![("127.0.0.1".to_owned(), 3000)]);
assert_eq!(conf1.ws_config().unwrap().origins, None);
assert_eq!(conf2.directories().signer, "signer".to_owned());
assert_eq!(conf2.ui_config(), UiConfiguration {