Add a browser-utils crate (#4394)

* Squash

* Fix keystore on wasm

* Update utils/browser/Cargo.toml

Co-Authored-By: Benjamin Kampmann <ben@gnunicorn.org>

* export console functions

* Use an Option<PathBuf> in keystore instead of cfg flags

* Add a KeystoreConfig

* Update libp2p

* Bump kvdb-web version

* Fix cli

* Upgrade versions

* Update wasm-bindgen stuff

Co-authored-by: Benjamin Kampmann <ben.kampmann@googlemail.com>
This commit is contained in:
Ashley
2020-01-07 16:30:04 +01:00
committed by GitHub
parent d76a33033d
commit bb44f8fc24
13 changed files with 432 additions and 254 deletions
+4 -17
View File
@@ -87,15 +87,9 @@ ctrlc = { version = "3.1.3", features = ["termination"], optional = true }
node-transaction-factory = { version = "2.0.0", optional = true, path = "../transaction-factory" }
# WASM-specific dependencies
libp2p = { version = "0.13.2", default-features = false, optional = true }
clear_on_drop = { version = "0.2.3", features = ["no_cc"], optional = true } # Imported just for the `no_cc` feature
console_error_panic_hook = { version = "0.1.1", optional = true }
console_log = { version = "0.1.2", optional = true }
js-sys = { version = "0.3.22", optional = true }
wasm-bindgen = { version = "0.2.45", optional = true }
wasm-bindgen-futures = { version = "0.3.22", optional = true }
kvdb-memorydb = { version = "0.2.0", optional = true }
rand6 = { package = "rand", version = "0.6", features = ["wasm-bindgen"], optional = true } # Imported just for the `wasm-bindgen` feature
wasm-bindgen = { version = "0.2.57", optional = true }
wasm-bindgen-futures = { version = "0.4.7", optional = true }
browser-utils = { path = "../../../utils/browser", optional = true }
[dev-dependencies]
sc-keystore = { version = "2.0.0", path = "../../../client/keystore" }
@@ -113,16 +107,9 @@ vergen = "3.0.4"
[features]
default = ["cli"]
browser = [
"clear_on_drop",
"console_error_panic_hook",
"console_log",
"js-sys",
"libp2p",
"browser-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
"kvdb-memorydb",
"rand/wasm-bindgen",
"rand6"
]
cli = [
"sc-cli",
+1 -1
View File
@@ -1,3 +1,3 @@
#!/usr/bin/env sh
wasm-pack build --target web --out-dir ./browser-demo/pkg --no-typescript --release ./.. -- --no-default-features --features "browser"
python -m SimpleHTTPServer 8000
python -m http.server 8000
@@ -19,7 +19,7 @@ async function start() {
// Build our client.
log('Starting client');
let client = start_client(ws());
let client = await start_client(ws());
log('Client started');
client.rpcSubscribe('{"method":"chain_subscribeNewHead","params":[],"id":1,"jsonrpc":"2.0"}',
@@ -29,7 +29,7 @@ async function start() {
client
.rpcSend('{"method":"system_networkState","params":[],"id":1,"jsonrpc":"2.0"}')
.then((r) => log("Network state: " + r));
}, 1000);
}, 20000);
}
start();
+19 -123
View File
@@ -15,45 +15,31 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::ChainSpec;
use futures01::{prelude::*, sync::oneshot, sync::mpsc};
use libp2p::wasm_ext;
use log::{debug, info};
use std::sync::Arc;
use sc_service::{AbstractService, RpcSession, Roles as ServiceRoles, Configuration, config::DatabaseConfig};
use log::info;
use wasm_bindgen::prelude::*;
use sc_service::Configuration;
use browser_utils::{
Transport, Client,
browser_configuration, set_console_error_panic_hook, init_console_log,
};
/// Starts the client.
///
/// You must pass a libp2p transport that supports .
#[wasm_bindgen]
pub fn start_client(wasm_ext: wasm_ext::ffi::Transport) -> Result<Client, JsValue> {
pub async fn start_client(wasm_ext: Transport) -> Result<Client, JsValue> {
start_inner(wasm_ext)
.await
.map_err(|err| JsValue::from_str(&err.to_string()))
}
fn start_inner(wasm_ext: wasm_ext::ffi::Transport) -> Result<Client, Box<dyn std::error::Error>> {
console_error_panic_hook::set_once();
console_log::init_with_level(log::Level::Info);
async fn start_inner(wasm_ext: Transport) -> Result<Client, Box<dyn std::error::Error>> {
set_console_error_panic_hook();
init_console_log(log::Level::Info)?;
// Build the configuration to pass to the service.
let config = {
let wasm_ext = wasm_ext::ExtTransport::new(wasm_ext);
let chain_spec = ChainSpec::FlamingFir.load().map_err(|e| format!("{:?}", e))?;
let mut config = Configuration::<(), _, _>::default_with_spec_and_base_path(chain_spec, None);
config.network.transport = sc_network::config::TransportConfig::Normal {
wasm_external_transport: Some(wasm_ext.clone()),
allow_private_ipv4: true,
enable_mdns: false,
};
config.telemetry_external_transport = Some(wasm_ext);
config.roles = ServiceRoles::LIGHT;
config.name = "Browser node".to_string();
config.database = {
let db = Arc::new(kvdb_memorydb::create(10));
DatabaseConfig::Custom(db)
};
config
};
let chain_spec = ChainSpec::FlamingFir.load()
.map_err(|e| format!("{:?}", e))?;
let config: Configuration<(), _, _> = browser_configuration(wasm_ext, chain_spec)
.await?;
info!("Substrate browser node");
info!(" version {}", config.full_version());
@@ -63,98 +49,8 @@ fn start_inner(wasm_ext: wasm_ext::ffi::Transport) -> Result<Client, Box<dyn std
info!("Roles: {:?}", config.roles);
// Create the service. This is the most heavy initialization step.
let mut service = crate::service::new_light(config).map_err(|e| format!("{:?}", e))?;
let service = crate::service::new_light(config)
.map_err(|e| format!("{:?}", e))?;
// We now dispatch a background task responsible for processing the service.
//
// The main action performed by the code below consists in polling the service with
// `service.poll()`.
// The rest consists in handling RPC requests.
let (rpc_send_tx, mut rpc_send_rx) = mpsc::unbounded::<RpcMessage>();
wasm_bindgen_futures::spawn_local(futures01::future::poll_fn(move || {
loop {
match rpc_send_rx.poll() {
Ok(Async::Ready(Some(message))) => {
let fut = service.rpc_query(&message.session, &message.rpc_json);
let _ = message.send_back.send(Box::new(fut));
},
Ok(Async::NotReady) => break,
Err(_) | Ok(Async::Ready(None)) => return Ok(Async::Ready(())),
}
}
loop {
match service.poll().map_err(|_| ())? {
Async::Ready(()) => return Ok(Async::Ready(())),
Async::NotReady => break
}
}
Ok(Async::NotReady)
}));
Ok(Client {
rpc_send_tx,
})
}
/// A running client.
#[wasm_bindgen]
pub struct Client {
rpc_send_tx: mpsc::UnboundedSender<RpcMessage>,
}
struct RpcMessage {
rpc_json: String,
session: RpcSession,
send_back: oneshot::Sender<Box<dyn Future<Item = Option<String>, Error = ()>>>,
}
#[wasm_bindgen]
impl Client {
/// Allows starting an RPC request. Returns a `Promise` containing the result of that request.
#[wasm_bindgen(js_name = "rpcSend")]
pub fn rpc_send(&mut self, rpc: &str) -> js_sys::Promise {
let rpc_session = RpcSession::new(mpsc::channel(1).0);
let (tx, rx) = oneshot::channel();
let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
rpc_json: rpc.to_owned(),
session: rpc_session,
send_back: tx,
});
let fut = rx
.map_err(|_| ())
.and_then(|fut| fut)
.map(|s| JsValue::from_str(&s.unwrap_or(String::new())))
.map_err(|_| JsValue::NULL);
wasm_bindgen_futures::future_to_promise(fut)
}
/// Subscribes to an RPC pubsub endpoint.
#[wasm_bindgen(js_name = "rpcSubscribe")]
pub fn rpc_subscribe(&mut self, rpc: &str, callback: js_sys::Function) {
let (tx, rx) = mpsc::channel(4);
let rpc_session = RpcSession::new(tx);
let (fut_tx, fut_rx) = oneshot::channel();
let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
rpc_json: rpc.to_owned(),
session: rpc_session.clone(),
send_back: fut_tx,
});
let fut_rx = fut_rx
.map_err(|_| ())
.and_then(|fut| fut);
wasm_bindgen_futures::spawn_local(fut_rx.then(|_| Ok(())));
wasm_bindgen_futures::spawn_local(rx.for_each(move |s| {
match callback.call1(&callback, &JsValue::from_str(&s)) {
Ok(_) => Ok(()),
Err(_) => Err(()),
}
}).then(move |v| {
// We need to keep `rpc_session` alive.
debug!("RPC subscription has ended");
drop(rpc_session);
v
}));
}
Ok(browser_utils::start_client(service))
}