diff --git a/js/src/embed.js b/js/src/embed.js new file mode 100644 index 000000000..f228d264e --- /dev/null +++ b/js/src/embed.js @@ -0,0 +1,120 @@ +// Copyright 2015, 2016 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 'babel-polyfill'; +import 'whatwg-fetch'; + +import es6Promise from 'es6-promise'; +es6Promise.polyfill(); + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppContainer } from 'react-hot-loader'; + +import injectTapEventPlugin from 'react-tap-event-plugin'; + +import SecureApi from '~/secureApi'; +import ContractInstances from '~/contracts'; + +import { initStore } from '~/redux'; +import ContextProvider from '~/ui/ContextProvider'; +import muiTheme from '~/ui/Theme'; + +import { patchApi } from '~/util/tx'; +import { setApi } from '~/redux/providers/apiActions'; + +import '~/environment'; + +import '../assets/fonts/Roboto/font.css'; +import '../assets/fonts/RobotoMono/font.css'; + +injectTapEventPlugin(); + +import ParityBar from '~/views/ParityBar'; + +// Test transport (std transport should be provided as global object) +class FakeTransport { + constructor () { + console.warn('Secure Transport not provided. Falling back to FakeTransport'); + } + + execute (method, ...params) { + console.log('Calling', method, params); + return Promise.reject('not connected'); + } + + on () { + } +} + +class FrameSecureApi extends SecureApi { + constructor (transport) { + super('', null, () => { + return transport; + }); + } + + connect () { + // Do nothing - this API does not need connecting + this.emit('connecting'); + // Fire connected event with some delay. + setTimeout(() => { + this.emit('connected'); + }); + } + + needsToken () { + return false; + } + + isConnecting () { + return false; + } + + isConnected () { + return true; + } +} + +const api = new FrameSecureApi(window.secureTransport || new FakeTransport()); + +patchApi(api); +ContractInstances.create(api); + +const store = initStore(api, null, true); + +store.dispatch({ type: 'initAll', api }); +store.dispatch(setApi(api)); + +window.secureApi = api; + +const app = ( + +); +const container = document.querySelector('#container'); + +ReactDOM.render( + + + { app } + + , + container +); diff --git a/js/src/index.ejs b/js/src/index.ejs index 48cd5c0c0..590070e8b 100644 --- a/js/src/index.ejs +++ b/js/src/index.ejs @@ -27,7 +27,7 @@
-
Loading +
Loading
diff --git a/js/src/redux/middleware.js b/js/src/redux/middleware.js index bffeddc98..baaef358e 100644 --- a/js/src/redux/middleware.js +++ b/js/src/redux/middleware.js @@ -25,24 +25,27 @@ import CertificationsMiddleware from './providers/certifications/middleware'; import ChainMiddleware from './providers/chainMiddleware'; import RegistryMiddleware from './providers/registry/middleware'; -export default function (api, browserHistory) { +export default function (api, browserHistory, forEmbed = false) { const errors = new ErrorsMiddleware(); const signer = new SignerMiddleware(api); const settings = new SettingsMiddleware(); - const status = statusMiddleware(); - const certifications = new CertificationsMiddleware(); - const routeMiddleware = routerMiddleware(browserHistory); const chain = new ChainMiddleware(); - const registry = new RegistryMiddleware(api); - const middleware = [ settings.toMiddleware(), signer.toMiddleware(), errors.toMiddleware(), - certifications.toMiddleware(), - chain.toMiddleware(), - registry + chain.toMiddleware() ]; + if (!forEmbed) { + const certifications = new CertificationsMiddleware().toMiddleware(); + const registry = new RegistryMiddleware(api); + + middleware.push(certifications, registry); + } + + const status = statusMiddleware(); + const routeMiddleware = browserHistory ? routerMiddleware(browserHistory) : []; + return middleware.concat(status, routeMiddleware, thunk); } diff --git a/js/src/redux/store.js b/js/src/redux/store.js index 132375784..dff56bd20 100644 --- a/js/src/redux/store.js +++ b/js/src/redux/store.js @@ -33,9 +33,9 @@ const storeCreation = window.devToolsExtension ? window.devToolsExtension()(createStore) : createStore; -export default function (api, browserHistory) { +export default function (api, browserHistory, forEmbed = false) { const reducers = initReducers(); - const middleware = initMiddleware(api, browserHistory); + const middleware = initMiddleware(api, browserHistory, forEmbed); const store = applyMiddleware(...middleware)(storeCreation)(reducers); BalancesProvider.instantiate(store, api); diff --git a/js/src/secureApi.js b/js/src/secureApi.js index b39ecaf3b..8ee0d5196 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -15,12 +15,12 @@ // along with Parity. If not, see . import { uniq } from 'lodash'; +import store from 'store'; import Api from './api'; import { LOG_KEYS, getLogger } from '~/config'; const log = getLogger(LOG_KEYS.Signer); -const sysuiToken = window.localStorage.getItem('sysuiToken'); export default class SecureApi extends Api { _isConnecting = false; @@ -31,13 +31,18 @@ export default class SecureApi extends Api { _dappsPort = 8080; _signerPort = 8180; - constructor (url, nextToken) { - const transport = new Api.Transport.Ws(url, sysuiToken, false); + static getTransport (url, sysuiToken) { + return new Api.Transport.Ws(url, sysuiToken, false); + } + + constructor (url, nextToken, getTransport = SecureApi.getTransport) { + const sysuiToken = store.get('sysuiToken'); + const transport = getTransport(url, sysuiToken); + super(transport); this._url = url; - - // Try tokens from localstorage, from hash and 'initial' + // Try tokens from localStorage, from hash and 'initial' this._tokens = uniq([sysuiToken, nextToken, 'initial']) .filter((token) => token) .map((token) => ({ value: token, tried: false })); @@ -308,7 +313,7 @@ export default class SecureApi extends Api { } _saveToken (token) { - window.localStorage.setItem('sysuiToken', token); + store.set('sysuiToken', token); } /** diff --git a/js/src/ui/Tooltips/reducers.js b/js/src/ui/Tooltips/reducers.js index cc0ebf926..1d05de954 100644 --- a/js/src/ui/Tooltips/reducers.js +++ b/js/src/ui/Tooltips/reducers.js @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import store from 'store'; + const LS_KEY = 'tooltips'; let currentId = -1; let maxId = 0; function closeTooltips (state, action) { - window.localStorage.setItem(LS_KEY, '{"state":"off"}'); + store.set(LS_KEY, '{"state":"off"}'); currentId = -1; @@ -41,7 +43,7 @@ function newTooltip (state, action) { } function nextTooltip (state, action) { - const hideTips = window.localStorage.getItem(LS_KEY); + const hideTips = store.get(LS_KEY); currentId = hideTips ? -1 diff --git a/js/src/views/Application/store.js b/js/src/views/Application/store.js index 535d0942b..cc1968f37 100644 --- a/js/src/views/Application/store.js +++ b/js/src/views/Application/store.js @@ -15,17 +15,14 @@ // along with Parity. If not, see . import { action, observable } from 'mobx'; +import store from 'store'; export default class Store { @observable firstrunVisible = false; constructor (api) { this._api = api; - - const value = window.localStorage.getItem('showFirstRun'); - if (value) { - this.firstrunVisible = JSON.parse(value); - } + this.firstrunVisible = store.get('showFirstRun'); this._checkAccounts(); } @@ -36,7 +33,7 @@ export default class Store { @action toggleFirstrun = (visible = false) => { this.firstrunVisible = visible; - window.localStorage.setItem('showFirstRun', JSON.stringify(!!visible)); + store.set('showFirstRun', !!visible); } _checkAccounts () { diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index 8893ac566..1d5dd928c 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -23,15 +23,16 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; import { Badge, Button, ContainerTitle, ParityBackground } from '~/ui'; import { Embedded as Signer } from '../Signer'; -import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text.svg'; +import imagesEthcoreBlock from '!url-loader!../../../assets/images/parity-logo-white-no-text.svg'; import styles from './parityBar.css'; class ParityBar extends Component { static propTypes = { - pending: PropTypes.array, - dapp: PropTypes.bool - } + dapp: PropTypes.bool, + externalLink: PropTypes.string, + pending: PropTypes.array + }; state = { opened: false @@ -46,12 +47,32 @@ class ParityBar extends Component { } if (count < newCount) { - this.setState({ opened: true }); + this.setOpened(true); } else if (newCount === 0 && count === 1) { - this.setState({ opened: false }); + this.setOpened(false); } } + setOpened (opened) { + this.setState({ opened }); + + if (!this.bar) { + return; + } + + // Fire up custom even to support having parity bar iframed. + const event = new CustomEvent('parity.bar.visibility', { + bubbles: true, + detail: { opened } + }); + + this.bar.dispatchEvent(event); + } + + onRef = (element) => { + this.bar = element; + } + render () { const { opened } = this.state; @@ -73,27 +94,55 @@ class ParityBar extends Component { className={ styles.parityIcon } /> ); + const parityButton = ( +
); } + renderLink (button) { + const { externalLink } = this.props; + + if (!externalLink) { + return ( + + { button } + + ); + } + + return ( + + { button } + + ); + } + renderExpanded () { return (
@@ -147,9 +196,7 @@ class ParityBar extends Component { toggleDisplay = () => { const { opened } = this.state; - this.setState({ - opened: !opened - }); + this.setOpened(!opened); } } diff --git a/js/src/views/Settings/middleware.js b/js/src/views/Settings/middleware.js index 9ee674d76..71a6368be 100644 --- a/js/src/views/Settings/middleware.js +++ b/js/src/views/Settings/middleware.js @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import store from 'store'; + import defaultViews from './Views/defaults'; function initBackground (store, api) { @@ -26,11 +28,12 @@ function initBackground (store, api) { } function loadBackground () { - return window.localStorage.getItem('backgroundSeed'); + // Check global object to support embedding + return store.get('backgroundSeed') || window.backgroundSeed; } function saveBackground (backgroundSeed) { - window.localStorage.setItem('backgroundSeed', backgroundSeed); + store.set('backgroundSeed', backgroundSeed); } function initViews (store) { @@ -75,9 +78,9 @@ function loadViews () { let data; try { - const json = window.localStorage.getItem('views') || '{}'; + const json = store.get('views') || {}; - data = Object.assign(defaults, JSON.parse(json), fixed); + data = Object.assign(defaults, json, fixed); } catch (e) { data = defaults; } @@ -85,16 +88,16 @@ function loadViews () { return data; } -function saveViews (store) { - window.localStorage.setItem('views', JSON.stringify(getDefaultViews())); +function saveViews () { + store.set('views', getDefaultViews()); } -function toggleViews (store, viewIds) { +function toggleViews (viewIds) { viewIds.forEach((id) => { defaultViews[id].active = !defaultViews[id].active; }); - saveViews(store); + saveViews(); } export default class SettingsMiddleware { @@ -107,11 +110,11 @@ export default class SettingsMiddleware { break; case 'toggleView': - toggleViews(store, [action.viewId]); + toggleViews([action.viewId]); break; case 'toggleViews': - toggleViews(store, action.viewIds); + toggleViews(action.viewIds); break; case 'updateBackground': diff --git a/js/webpack/app.js b/js/webpack/app.js index 3375ff178..49fdac8af 100644 --- a/js/webpack/app.js +++ b/js/webpack/app.js @@ -40,7 +40,8 @@ module.exports = { context: path.join(__dirname, '../src'), entry: Object.assign({}, Shared.dappsEntry, { - index: './index.js' + index: './index.js', + embed: './embed.js' }), output: { // publicPath: '/', @@ -173,6 +174,17 @@ module.exports = { ] }), + new HtmlWebpackPlugin({ + title: 'Parity Bar', + filename: 'embed.html', + template: './index.ejs', + favicon: FAVICON, + chunks: [ + isProd ? null : 'commons', + 'embed' + ] + }), + new ScriptExtHtmlWebpackPlugin({ sync: [ 'commons', 'vendor.js' ], defaultAttribute: 'defer'