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
}
+
);