diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs
index 376b8a36f78..c38c6784a5e 100644
--- a/dapps/src/apps/mod.rs
+++ b/dapps/src/apps/mod.rs
@@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
@@ -30,8 +29,8 @@ use {WebProxyTokens, ParentFrameSettings};
mod app;
mod cache;
-mod fs;
mod ui;
+pub mod fs;
pub mod fetcher;
pub mod manifest;
@@ -64,9 +63,10 @@ pub fn all_endpoints(
web_proxy_tokens: Arc,
remote: Remote,
fetch: F,
-) -> Endpoints {
+) -> (Vec, Endpoints) {
// fetch fs dapps at first to avoid overwriting builtins
- let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
+ let mut pages = fs::local_endpoints(dapps_path.clone(), embeddable.clone());
+ let local_endpoints: Vec = pages.keys().cloned().collect();
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
pages.insert(id, endpoint);
@@ -80,10 +80,10 @@ pub fn all_endpoints(
pages.insert("proxy".into(), ProxyPac::boxed(embeddable.clone(), dapps_domain.to_owned()));
pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
- Arc::new(pages)
+ (local_endpoints, pages)
}
-fn insert(pages: &mut BTreeMap>, id: &str, embed_at: Embeddable) {
+fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
pages.insert(id.to_owned(), Box::new(match embed_at {
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
Embeddable::No => PageEndpoint::new(T::default()),
diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs
index ea8fd0a3842..ea5825b7495 100644
--- a/dapps/src/endpoint.rs
+++ b/dapps/src/endpoint.rs
@@ -16,7 +16,6 @@
//! URL Endpoint traits
-use std::sync::Arc;
use std::collections::BTreeMap;
use hyper::{self, server, net};
@@ -39,7 +38,7 @@ pub struct EndpointInfo {
pub icon_url: String,
}
-pub type Endpoints = Arc>>;
+pub type Endpoints = BTreeMap>;
pub type Handler = server::Handler + Send;
pub trait Endpoint : Send + Sync {
diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs
index 0cb7024cc15..f57eb2c7cf9 100644
--- a/dapps/src/lib.rs
+++ b/dapps/src/lib.rs
@@ -69,9 +69,11 @@ mod web;
#[cfg(test)]
mod tests;
+use std::collections::HashMap;
+use std::mem;
use std::path::PathBuf;
use std::sync::Arc;
-use std::collections::HashMap;
+use util::RwLock;
use jsonrpc_http_server::{self as http, hyper, Origin};
@@ -101,31 +103,54 @@ impl WebProxyTokens for F where F: Fn(String) -> Option + Send + Sync
}
/// Current supported endpoints.
+#[derive(Default, Clone)]
pub struct Endpoints {
- endpoints: endpoint::Endpoints,
+ local_endpoints: Arc>>,
+ endpoints: Arc>,
+ dapps_path: PathBuf,
+ embeddable: Option,
}
impl Endpoints {
/// Returns a current list of app endpoints.
pub fn list(&self) -> Vec {
- self.endpoints.iter().filter_map(|(ref k, ref e)| {
+ self.endpoints.read().iter().filter_map(|(ref k, ref e)| {
e.info().map(|ref info| apps::App::from_info(k, info))
}).collect()
}
+
+ /// Check for any changes in the local dapps folder and update.
+ pub fn refresh_local_dapps(&self) {
+ let new_local = apps::fs::local_endpoints(&self.dapps_path, self.embeddable.clone());
+ let old_local = mem::replace(&mut *self.local_endpoints.write(), new_local.keys().cloned().collect());
+ let (_, to_remove): (_, Vec<_>) = old_local
+ .into_iter()
+ .partition(|k| new_local.contains_key(&k.clone()));
+
+ let mut endpoints = self.endpoints.write();
+ // remove the dead dapps
+ for k in to_remove {
+ endpoints.remove(&k);
+ }
+ // new dapps to be added
+ for (k, v) in new_local {
+ if !endpoints.contains_key(&k) {
+ endpoints.insert(k, v);
+ }
+ }
+ }
}
/// Dapps server as `jsonrpc-http-server` request middleware.
pub struct Middleware {
+ endpoints: Endpoints,
router: router::Router,
- endpoints: endpoint::Endpoints,
}
impl Middleware {
/// Get local endpoints handle.
- pub fn endpoints(&self) -> Endpoints {
- Endpoints {
- endpoints: self.endpoints.clone(),
- }
+ pub fn endpoints(&self) -> &Endpoints {
+ &self.endpoints
}
/// Creates new middleware for UI server.
@@ -164,8 +189,8 @@ impl Middleware {
);
Middleware {
- router: router,
endpoints: Default::default(),
+ router: router,
}
}
@@ -191,8 +216,8 @@ impl Middleware {
remote.clone(),
fetch.clone(),
).embeddable_on(embeddable.clone()).allow_dapps(true));
- let endpoints = apps::all_endpoints(
- dapps_path,
+ let (local_endpoints, endpoints) = apps::all_endpoints(
+ dapps_path.clone(),
extra_dapps,
dapps_domain,
embeddable.clone(),
@@ -200,6 +225,12 @@ impl Middleware {
remote.clone(),
fetch.clone(),
);
+ let endpoints = Endpoints {
+ endpoints: Arc::new(RwLock::new(endpoints)),
+ dapps_path,
+ local_endpoints: Arc::new(RwLock::new(local_endpoints)),
+ embeddable: embeddable.clone(),
+ };
let special = {
let mut special = special_endpoints(
@@ -225,8 +256,8 @@ impl Middleware {
);
Middleware {
- router: router,
- endpoints: endpoints,
+ endpoints,
+ router,
}
}
}
diff --git a/dapps/src/router.rs b/dapps/src/router.rs
index 5cf92ff7e19..2b74d51dfde 100644
--- a/dapps/src/router.rs
+++ b/dapps/src/router.rs
@@ -28,7 +28,8 @@ use jsonrpc_http_server as http;
use apps;
use apps::fetcher::Fetcher;
-use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
+use endpoint::{Endpoint, EndpointPath, Handler};
+use Endpoints;
use handlers;
use Embeddable;
@@ -50,26 +51,27 @@ pub struct Router {
dapps_domain: String,
}
-impl http::RequestMiddleware for Router {
- fn on_request(&self, req: &server::Request, control: &Control) -> http::RequestMiddlewareAction {
+impl Router {
+ fn resolve_request(&self, req: &server::Request, control: Control, refresh_dapps: bool) -> (bool, Option>) {
// Choose proper handler depending on path / domain
let url = handlers::extract_url(req);
let endpoint = extract_endpoint(&url, &self.dapps_domain);
let referer = extract_referer_endpoint(req, &self.dapps_domain);
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
- let is_origin_set = req.headers().get::().is_some();
let is_get_request = *req.method() == hyper::Method::Get;
let is_head_request = *req.method() == hyper::Method::Head;
+ let has_dapp = |dapp: &str| self.endpoints
+ .as_ref()
+ .map_or(false, |endpoints| endpoints.endpoints.read().contains_key(dapp));
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
-
- let control = control.clone();
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
- let handler: Option> = match (endpoint.0, endpoint.1, referer) {
+
+ (is_utils, match (endpoint.0, endpoint.1, referer) {
// Handle invalid web requests that we can recover from
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
if referer.app_id == apps::WEB_PATH
- && self.endpoints.as_ref().map(|ep| ep.contains_key(apps::WEB_PATH)).unwrap_or(false)
+ && has_dapp(apps::WEB_PATH)
&& !is_web_endpoint(path)
=>
{
@@ -88,11 +90,13 @@ impl http::RequestMiddleware for Router {
.map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control))
},
// Then delegate to dapp
- (Some(ref path), _, _) if self.endpoints.as_ref().map(|ep| ep.contains_key(&path.app_id)).unwrap_or(false) => {
+ (Some(ref path), _, _) if has_dapp(&path.app_id) => {
trace!(target: "dapps", "Resolving to local/builtin dapp.");
Some(self.endpoints
.as_ref()
.expect("endpoints known to be set; qed")
+ .endpoints
+ .read()
.get(&path.app_id)
.expect("endpoints known to contain key; qed")
.to_async_handler(path.clone(), control))
@@ -110,13 +114,19 @@ impl http::RequestMiddleware for Router {
=>
{
trace!(target: "dapps", "Resolving to 404.");
- Some(Box::new(handlers::ContentHandler::error(
- hyper::StatusCode::NotFound,
- "404 Not Found",
- "Requested content was not found.",
- None,
- self.embeddable_on.clone(),
- )))
+ if refresh_dapps {
+ debug!(target: "dapps", "Refreshing dapps and re-trying.");
+ self.endpoints.as_ref().map(|endpoints| endpoints.refresh_local_dapps());
+ return self.resolve_request(req, control, false)
+ } else {
+ Some(Box::new(handlers::ContentHandler::error(
+ hyper::StatusCode::NotFound,
+ "404 Not Found",
+ "Requested content was not found.",
+ None,
+ self.embeddable_on.clone(),
+ )))
+ }
},
// Any other GET|HEAD requests to home page.
_ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => {
@@ -130,8 +140,15 @@ impl http::RequestMiddleware for Router {
trace!(target: "dapps", "Resolving to RPC call.");
None
}
- };
+ })
+ }
+}
+impl http::RequestMiddleware for Router {
+ fn on_request(&self, req: &server::Request, control: &Control) -> http::RequestMiddlewareAction {
+ let control = control.clone();
+ let is_origin_set = req.headers().get::().is_some();
+ let (is_utils, handler) = self.resolve_request(req, control, self.endpoints.is_some());
match handler {
Some(handler) => http::RequestMiddlewareAction::Respond {
should_validate_hosts: !is_utils,
diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs
index 8abc86196c6..f123231554c 100644
--- a/dapps/src/tests/fetch.rs
+++ b/dapps/src/tests/fetch.rs
@@ -39,7 +39,7 @@ fn should_resolve_dapp() {
// then
response.assert_status("HTTP/1.1 404 Not Found");
- assert_eq!(registrar.calls.lock().len(), 2);
+ assert_eq!(registrar.calls.lock().len(), 4);
assert_security_headers_for_embed(&response.headers);
}
diff --git a/dapps/src/tests/redirection.rs b/dapps/src/tests/redirection.rs
index 1e9b039e2ea..81d3ec76cc8 100644
--- a/dapps/src/tests/redirection.rs
+++ b/dapps/src/tests/redirection.rs
@@ -204,4 +204,3 @@ fn should_serve_utils() {
assert_eq!(response.body.contains("function(){"), true);
assert_security_headers(&response.headers);
}
-
diff --git a/dapps/src/web.rs b/dapps/src/web.rs
index 637a5287ea5..5222f51b5ba 100644
--- a/dapps/src/web.rs
+++ b/dapps/src/web.rs
@@ -241,5 +241,3 @@ impl server::Handler for WebHandler {
}
}
}
-
-
diff --git a/js/package-lock.json b/js/package-lock.json
index 6324f5ab864..4983b763572 100644
--- a/js/package-lock.json
+++ b/js/package-lock.json
@@ -7722,7 +7722,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"requires": {
"brace-expansion": "1.1.8"
}
@@ -10081,7 +10081,7 @@
"react-qr-reader": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/react-qr-reader/-/react-qr-reader-1.1.3.tgz",
- "integrity": "sha512-ruBF8KaSwUW9nbzjO4rA7/HOCGYZuNUz9od7uBRy8SRBi24nwxWWmwa2z8R6vPGDRglA0y2Qk1aVBuC1olTnHw==",
+ "integrity": "sha1-dDmnZvyZPLj17u/HLCnblh1AswI=",
"requires": {
"jsqr": "git+https://github.com/JodusNodus/jsQR.git#5ba1acefa1cbb9b2bc92b49f503f2674e2ec212b",
"prop-types": "15.5.10",
diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js
index 4fdaf5b1beb..c2681b3fbc0 100644
--- a/js/src/api/rpc/parity/parity.js
+++ b/js/src/api/rpc/parity/parity.js
@@ -95,6 +95,11 @@ export default class Parity {
.execute('parity_dappsList');
}
+ dappsRefresh () {
+ return this._transport
+ .execute('parity_dappsRefresh');
+ }
+
dappsUrl () {
return this._transport
.execute('parity_dappsUrl');
diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js
index d1ade602bec..d80a0c5a235 100644
--- a/js/src/jsonrpc/interfaces/parity.js
+++ b/js/src/jsonrpc/interfaces/parity.js
@@ -164,6 +164,17 @@ export default {
}
},
+ dappsRefresh: {
+ subdoc: SUBDOC_SET,
+ desc: 'Returns a boolean value upon success and error upon failure',
+ params: [],
+ returns: {
+ type: Boolean,
+ desc: 'True for success. error details for failure',
+ example: true
+ }
+ },
+
dappsUrl: {
section: SECTION_NODE,
desc: 'Returns the hostname and the port of dapps/rpc server, error if not enabled.',
diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js
index 5e5efedf79e..54706f3521c 100644
--- a/js/src/views/Dapps/dapps.js
+++ b/js/src/views/Dapps/dapps.js
@@ -24,7 +24,7 @@ import { connect } from 'react-redux';
import { DappPermissions, DappsVisible } from '~/modals';
import PermissionStore from '~/modals/DappPermissions/store';
import { Actionbar, Button, DappCard, Page, SectionList } from '~/ui';
-import { LockedIcon, VisibleIcon } from '~/ui/Icons';
+import { LockedIcon, RefreshIcon, VisibleIcon } from '~/ui/Icons';
import DappsStore from './dappsStore';
@@ -90,6 +90,17 @@ class Dapps extends Component {
/>
}
buttons={ [
+ }
+ key='refresh'
+ label={
+
+ }
+ onClick={ this.store.refreshDapps }
+ />,
}
key='edit'
diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js
index b0570dc8f38..1a5e921ea74 100644
--- a/js/src/views/Dapps/dappsStore.js
+++ b/js/src/views/Dapps/dappsStore.js
@@ -89,7 +89,7 @@ export default class DappsStore extends EventEmitter {
return Promise
.all([
this.fetchBuiltinApps().then((apps) => this.addApps(apps)),
- this.fetchLocalApps().then((apps) => this.addApps(apps))
+ this.fetchLocalApps().then((apps) => this.addApps(apps, true))
]);
}
@@ -227,6 +227,20 @@ export default class DappsStore extends EventEmitter {
return this.visibleApps.filter((app) => app.type === 'network');
}
+ @action refreshDapps = () => {
+ const self = this;
+
+ self._api.parity.dappsRefresh()
+ .then((res) => {
+ if (res === true) {
+ self.loadAllApps();
+ }
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ }
+
@action openModal = () => {
this.modalOpen = true;
}
@@ -266,7 +280,7 @@ export default class DappsStore extends EventEmitter {
this.displayApps = Object.assign({}, this.displayApps, displayApps);
};
- @action addApps = (_apps = []) => {
+ @action addApps = (_apps = [], _local = false) => {
transaction(() => {
const apps = _apps.filter((app) => app);
@@ -277,6 +291,7 @@ export default class DappsStore extends EventEmitter {
this.apps = this.apps
.filter((app) => !app.id || !newAppsIds.includes(app.id))
+ .filter((app) => !(app.type === 'local' && _local && apps.indexOf(app) === -1))
.concat(apps || [])
.sort((a, b) => a.name.localeCompare(b.name));
diff --git a/parity/dapps.rs b/parity/dapps.rs
index cec3765f256..241e38cb424 100644
--- a/parity/dapps.rs
+++ b/parity/dapps.rs
@@ -286,7 +286,7 @@ mod server {
pub fn service(middleware: &Option) -> Option> {
middleware.as_ref().map(|m| Arc::new(DappsServiceWrapper {
- endpoints: m.endpoints()
+ endpoints: m.endpoints().clone(),
}) as Arc)
}
@@ -308,5 +308,10 @@ mod server {
})
.collect()
}
+
+ fn refresh_local_dapps(&self) -> bool {
+ self.endpoints.refresh_local_dapps();
+ true
+ }
}
}
diff --git a/rpc/src/v1/helpers/dapps.rs b/rpc/src/v1/helpers/dapps.rs
index 34f4fe1b5dd..391a12c824d 100644
--- a/rpc/src/v1/helpers/dapps.rs
+++ b/rpc/src/v1/helpers/dapps.rs
@@ -22,12 +22,6 @@ use v1::types::LocalDapp;
pub trait DappsService: Send + Sync + 'static {
/// List available local dapps.
fn list_dapps(&self) -> Vec;
-}
-
-impl DappsService for F where
- F: Fn() -> Vec + Send + Sync + 'static
-{
- fn list_dapps(&self) -> Vec {
- (*self)()
- }
+ /// Refresh local dapps list
+ fn refresh_local_dapps(&self) -> bool;
}
diff --git a/rpc/src/v1/impls/light/parity_set.rs b/rpc/src/v1/impls/light/parity_set.rs
index fdeee693f68..b973271683f 100644
--- a/rpc/src/v1/impls/light/parity_set.rs
+++ b/rpc/src/v1/impls/light/parity_set.rs
@@ -135,6 +135,10 @@ impl ParitySet for ParitySetClient {
}))
}
+ fn dapps_refresh(&self) -> Result {
+ self.dapps.as_ref().map(|dapps| dapps.refresh_local_dapps()).ok_or_else(errors::dapps_disabled)
+ }
+
fn dapps_list(&self) -> Result, Error> {
self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled)
}
diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs
index dc771c4f8a4..540964e637b 100644
--- a/rpc/src/v1/impls/parity_set.rs
+++ b/rpc/src/v1/impls/parity_set.rs
@@ -176,6 +176,10 @@ impl ParitySet for ParitySetClient where
}))
}
+ fn dapps_refresh(&self) -> Result {
+ self.dapps.as_ref().map(|dapps| dapps.refresh_local_dapps()).ok_or_else(errors::dapps_disabled)
+ }
+
fn dapps_list(&self) -> Result, Error> {
self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled)
}
diff --git a/rpc/src/v1/tests/helpers/dapps.rs b/rpc/src/v1/tests/helpers/dapps.rs
index 44148ab0f1c..6a7182dd0ff 100644
--- a/rpc/src/v1/tests/helpers/dapps.rs
+++ b/rpc/src/v1/tests/helpers/dapps.rs
@@ -34,4 +34,8 @@ impl DappsService for TestDappsService {
icon_url: "title.png".into(),
}]
}
+
+ fn refresh_local_dapps(&self) -> bool {
+ true
+ }
}
diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs
index 1feb39718d0..15f35b67148 100644
--- a/rpc/src/v1/traits/parity_set.rs
+++ b/rpc/src/v1/traits/parity_set.rs
@@ -96,6 +96,10 @@ build_rpc_trait! {
#[rpc(async, name = "parity_hashContent")]
fn hash_content(&self, String) -> BoxFuture;
+ /// Returns true if refresh successful, error if unsuccessful or server is disabled.
+ #[rpc(name = "parity_dappsRefresh")]
+ fn dapps_refresh(&self) -> Result;
+
/// Returns a list of local dapps
#[rpc(name = "parity_dappsList")]
fn dapps_list(&self) -> Result, Error>;