diff --git a/js/package.json b/js/package.json index 9eb0cd804..ff3320f50 100644 --- a/js/package.json +++ b/js/package.json @@ -193,6 +193,7 @@ "scryptsy": "2.0.0", "solc": "ngotchac/solc-js", "store": "1.3.20", + "useragent.js": "0.5.6", "utf8": "2.1.2", "valid-url": "1.0.9", "validator": "6.2.0", diff --git a/js/src/views/Application/Extension/extension.css b/js/src/views/Application/Extension/extension.css new file mode 100644 index 000000000..98d094a9f --- /dev/null +++ b/js/src/views/Application/Extension/extension.css @@ -0,0 +1,52 @@ +/* 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 . +*/ + +.body { + background: #f80; + color: white; + opacity: 1; + max-width: 500px; + padding: 1em 4em 1em 2em; + position: fixed; + right: 1.5em; + top: 1.5em; + z-index: 1000; + + .button { + background: rgba(0, 0, 0, 0.5); + color: white !important; + + svg { + fill: white !important; + } + } + + .buttonrow { + text-align: right; + } + + p { + color: white; + } + + .close { + cursor: pointer; + position: absolute; + right: 1em; + top: 1em; + } +} diff --git a/js/src/views/Application/Extension/extension.js b/js/src/views/Application/Extension/extension.js new file mode 100644 index 000000000..aff332f9a --- /dev/null +++ b/js/src/views/Application/Extension/extension.js @@ -0,0 +1,74 @@ +// 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 { observer } from 'mobx-react'; +import React, { Component } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Button } from '~/ui'; +import { CloseIcon, CheckIcon } from '~/ui/Icons'; + +import Store from './store'; +import styles from './extension.css'; + +@observer +export default class Extension extends Component { + store = new Store(); + + render () { + const { showWarning } = this.store; + + if (!showWarning) { + return null; + } + + return ( +
+ +

+ +

+

+

+ ); + } + + onClose = () => { + this.store.snoozeWarning(); + } + + onInstallClick = () => { + this.store.installExtension(); + } +} diff --git a/js/src/views/Application/Extension/index.js b/js/src/views/Application/Extension/index.js new file mode 100644 index 000000000..ac1cfa015 --- /dev/null +++ b/js/src/views/Application/Extension/index.js @@ -0,0 +1,17 @@ +// 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 from './extension'; diff --git a/js/src/views/Application/Extension/store.js b/js/src/views/Application/Extension/store.js new file mode 100644 index 000000000..40a3f09e7 --- /dev/null +++ b/js/src/views/Application/Extension/store.js @@ -0,0 +1,89 @@ +// 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 . + +/* global chrome */ + +import { action, computed, observable } from 'mobx'; + +import store from 'store'; +import browser from 'useragent.js/lib/browser'; + +const A_DAY = 24 * 60 * 60 * 1000; +const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay'; + +// 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig'; +const EXTENSION_PAGE = 'https://chrome.google.com/webstore/detail/himekenlppkgeaoeddcliojfddemadig'; + +export default class Store { + @observable isInstalling = false; + @observable nextDisplay = 0; + @observable shouldInstall = false; + + constructor () { + this.nextDisplay = store.get(NEXT_DISPLAY) || 0; + this.testInstall(); + } + + @computed get showWarning () { + return !this.isInstalling && this.shouldInstall && (Date.now() > this.nextDisplay); + } + + @action setInstalling = (isInstalling) => { + this.isInstalling = isInstalling; + } + + @action snoozeWarning = (sleep = A_DAY) => { + this.nextDisplay = Date.now() + sleep; + store.set(NEXT_DISPLAY, this.nextDisplay); + } + + @action testInstall = () => { + this.shouldInstall = this.readStatus(); + } + + readStatus = () => { + const hasExtension = Symbol.for('parity.extension') in window; + const ua = browser.analyze(navigator.userAgent || ''); + + if (hasExtension) { + return false; + } + + return (ua || {}).name.toLowerCase() === 'chrome'; + } + + installExtension = () => { + this.setInstalling(true); + + return new Promise((resolve, reject) => { + const link = document.createElement('link'); + + link.setAttribute('rel', 'chrome-webstore-item'); + link.setAttribute('href', EXTENSION_PAGE); + document.querySelector('head').appendChild(link); + + if (chrome && chrome.webstore && chrome.webstore.install) { + chrome.webstore.install(EXTENSION_PAGE, resolve, reject); + } else { + reject(new Error('Direct installation failed.')); + } + }) + .catch((error) => { + console.warn('Unable to perform direct install', error); + window.open(EXTENSION_PAGE, '_blank'); + }); + } +} diff --git a/js/src/views/Application/application.js b/js/src/views/Application/application.js index 4b905b3d4..377dcecbc 100644 --- a/js/src/views/Application/application.js +++ b/js/src/views/Application/application.js @@ -26,6 +26,7 @@ import ParityBar from '../ParityBar'; import Snackbar from './Snackbar'; import Container from './Container'; import DappContainer from './DappContainer'; +import Extension from './Extension'; import FrameError from './FrameError'; import Status from './Status'; import Store from './store'; @@ -106,6 +107,7 @@ class Application extends Component { ? : null } + );