Feature/wasm js demo (#191)

* Moved `nym-sphinx-wasm` into `wasm` folder and renamed its examples

* A proposal for some structural cleanup

* desktop client: beginning of a Rust example for chunking.

* Simplifying desktop client readme

* Tarting up webassembly README

* A more visual webassembly client example

* clients/webassembly: grouping a few things into named functions for clarity

* clients/webassembly: put URLs at top of file, prepping for drop-down action

* mixnode + provider run: changed startup error messages a bit

* clients/webassembly: scrolling from the bottom in the demo
This commit is contained in:
Dave Hrycyszyn
2020-04-21 10:05:38 +01:00
committed by GitHub
parent 4af5788bba
commit 8baa236d81
94 changed files with 181 additions and 86 deletions
+7 -7
View File
@@ -5,11 +5,13 @@ opt-level = "s"
[workspace]
members = [
"common/clients/directory-client",
"common/clients/mix-client",
"common/clients/multi-tcp-client",
"common/clients/provider-client",
"common/clients/validator-client",
"clients/desktop",
"clients/webassembly",
"common/client-libs/directory-client",
"common/client-libs/mix-client",
"common/client-libs/multi-tcp-client",
"common/client-libs/provider-client",
"common/client-libs/validator-client",
"common/config",
"common/crypto",
"common/healthcheck",
@@ -18,8 +20,6 @@ members = [
"common/topology",
"gateway",
"mixnode",
"nym-client",
"nym-sphinx-wasm",
"sfw-provider",
"sfw-provider/sfw-provider-requests",
"validator",
@@ -28,17 +28,17 @@ tokio = { version = "0.2", features = ["full"] }
tokio-tungstenite = "0.10.1"
## internal
config = {path = "../common/config"}
crypto = {path = "../common/crypto"}
directory-client = { path = "../common/clients/directory-client" }
healthcheck = { path = "../common/healthcheck" }
mix-client = { path = "../common/clients/mix-client" }
multi-tcp-client = { path = "../common/clients/multi-tcp-client" }
nymsphinx = { path = "../common/nymsphinx" }
pemstore = {path = "../common/pemstore"}
provider-client = { path = "../common/clients/provider-client" }
sfw-provider-requests = { path = "../sfw-provider/sfw-provider-requests" }
topology = {path = "../common/topology" }
config = {path = "../../common/config"}
crypto = {path = "../../common/crypto"}
directory-client = { path = "../../common/client-libs/directory-client" }
healthcheck = { path = "../../common/healthcheck" }
mix-client = { path = "../../common/client-libs/mix-client" }
multi-tcp-client = { path = "../../common/client-libs/multi-tcp-client" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = {path = "../../common/pemstore"}
provider-client = { path = "../../common/client-libs/provider-client" }
sfw-provider-requests = { path = "../../sfw-provider/sfw-provider-requests" }
topology = {path = "../../common/topology" }
[build-dependencies]
built = "0.3.2"
+3
View File
@@ -0,0 +1,3 @@
# Nym Desktop Client
The Nym Desktop Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use config::NymConfig;
use std::time;
use tokio::runtime::Runtime;
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.init();
}
fn main() {
// totally optional
setup_logging();
let mut rt = Runtime::new().unwrap();
// note: I have manually initialised the client with this config (because the keys weren't
// saved, etc - a really dodgy setup but good enough for a quick test)
// so to run it, you need to do the usual `nym-client init --id native-local`
// then optionally modify config.toml to increase rates, etc. if you want
let config = nym_client::config::Config::load_from_file(None, Some("native-local")).unwrap();
let mut client = nym_client::client::NymClient::new(config);
let input_data = std::fs::read("trailer.mp4").unwrap();
client.start();
let address = client.as_mix_destination();
client.send_message(address, input_data);
loop {
let mut messages = rt.block_on(async {
tokio::time::delay_for(time::Duration::from_secs(2)).await;
client.check_for_messages_async().await
});
if !messages.is_empty() {
assert_eq!(messages.len(), 1);
std::fs::write("downloaded.mp4", messages.pop().unwrap()).unwrap();
println!("done!");
std::process::exit(0);
}
}
}
@@ -17,8 +17,8 @@ slice_as_array = "1.1.0"
wasm-bindgen = "0.2"
# internal
crypto = { path = "../common/crypto" }
nymsphinx = { path = "../common/nymsphinx" }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
# The `console_error_panic_hook` crate provides better debugging of panics by
@@ -2,7 +2,7 @@
This is a Rust crate which is set up to automatically cross-compile the contents of `lib.rs` to WebAssembly (aka wasm).
Wasm is pretty close to bare metal. Browser-based or server-side JavaScript (or other wasm-using environments) can use the wasm output from this crate to create Sphinx packets at much higher speeds than would be possible using (interpreted) JavaScript. This helps browser-based and mobile applications get stronger privacy, in a way that wasn't previously possible.
Wasm is pretty close to bare metal. Browser-based or server-side JavaScript (or other wasm-using environments) can use the wasm output from this crate to create Sphinx packets at much higher speeds than would be possible using (interpreted) JavaScript. This enables browser-based and mobile applications get stronger privacy, in a way that wasn't previously possible.
### Compiling
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym WebAssembly Demo</title>
</head>
<body>
<p>
<label for="fname">Recipient address: </label><input type="text" id="recipient" name="recipient"
value="HNka47Wji3LF23wGtU5oAFwoaM3tfbp2fBLGBPLcwUZ5">
</p>
<label for="fname">Text to send: </label><input type="text" id="sendtext" name="sendtext" value="Hello mixnet!">
<button id="send-button">Send</button>
<p>Send messages to the mixnet using the "send" button.</p>
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
messages show in green.</p>
<hr>
<p>
<span id="output"></div>
</p>
<script src="./bootstrap.js"></script>
</body>
</html>
@@ -14,8 +14,6 @@
import * as wasm from "nym-sphinx-wasm";
console.log("Retrieving topology...");
class Route {
constructor(nodes) {
this.nodes = nodes;
@@ -29,20 +27,48 @@ class NodeData {
}
}
async function getTopology() {
let topologyURL = 'http://127.0.0.1:8080/api/presence/topology'; // TODO change this DAVE!!!!
let response = await makeRequest('get', topologyURL);
async function main() {
var gatewayUrl = "ws://127.0.0.1:1793";
var directoryUrl = "http://127.0.0.1:8080/api/presence/topology";
// Get the topology, then the mixnode and provider data
const topology = await getTopology(directoryUrl);
// Set up a websocket connection to the gateway node
var connection = await connectWebsocket(gatewayUrl).then(function (c) {
return c
}).catch(function (err) {
console.log("Websocket ERROR: " + err);
})
// Set up the send button
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function () {
sendMessageToMixnet(connection, topology);
}
}
// Create a Sphinx packet and send it to the mixnet through the Gateway node.
function sendMessageToMixnet(connection, topology) {
let route = constructRoute(topology);
var recipient = document.getElementById("recipient").value;
var sendText = document.getElementById("sendtext").value;
let packet = wasm.create_sphinx_packet(JSON.stringify(route), sendText, recipient);
connection.send(packet);
displaySend(packet);
display("Sent a Sphinx packet containing message: " + sendText);
}
async function getTopology(directoryUrl) {
let response = await http('get', directoryUrl);
let topology = JSON.parse(response);
return topology;
}
async function main() {
// Get the topology, then the mixnode and provider data
const topology = await getTopology();
// Construct a route from the current network topology so we can get wasm to build us a Sphinx packet
function constructRoute(topology) {
const mixnodes = topology.mixNodes;
const provider = topology.mixProviderNodes[0];
// Construct a route so we can get wasm to build us a Sphinx packet
let nodes = [];
mixnodes.forEach(node => {
let n = new NodeData(node.host, node.pubKey);
@@ -50,29 +76,23 @@ async function main() {
});
let p = new NodeData(provider.mixnetListener, provider.pubKey)
nodes.push(p);
let route = new Route(nodes);
// Create the packet
let packet = wasm.create_sphinx_packet(JSON.stringify(route), "THIS IS THE MESSAGE", "C2fdNoUybRuGrVYUM6QRejiELPQCohGbxjhKpU4UZ4ci");
// Set up a websocket connection to the gateway node
var port = "1793" // gateway websocket listens on 1793 by default, change if yours is different
var url = "ws://127.0.0.1:" + port;
connectWebsocket(url).then(function (server) {
server.send("hello gateway");
console.log(packet);
server.send(packet);
}).catch(function (err) {
console.log("Websocket ERROR: " + err);
})
return new Route(nodes);
}
// Let's get started!
main();
// utility functions below here, nothing too interesting...
function makeRequest(method, url) {
function display(message) {
document.getElementById("output").innerHTML = "<p>" + message + "</p >" + document.getElementById("output").innerHTML;
}
function displaySend(message) {
document.getElementById("output").innerHTML = "<p style='color: blue; word-break: break-all;'>sent >>> " + message + "</p >" + document.getElementById("output").innerHTML;
}
function http(method, url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(method, url);
@@ -96,12 +116,6 @@ function makeRequest(method, url) {
});
}
function print(name, obj) {
console.log(name + ": " + JSON.stringify(obj));
}
function connectWebsocket(url) {
return new Promise(function (resolve, reject) {
var server = new WebSocket(url);
+3 -3
View File
@@ -19,10 +19,10 @@ tokio = { version = "0.2", features = ["full"] }
## internal
crypto = { path = "../crypto" }
directory-client = { path = "../clients/directory-client" }
multi-tcp-client = { path = "../clients/multi-tcp-client" }
directory-client = { path = "../client-libs/directory-client" }
multi-tcp-client = { path = "../client-libs/multi-tcp-client" }
nymsphinx = {path = "../nymsphinx" }
provider-client = { path = "../clients/provider-client" }
provider-client = { path = "../client-libs/provider-client" }
sfw-provider-requests = { path = "../../sfw-provider/sfw-provider-requests" }
topology = {path = "../topology" }
+1 -1
View File
@@ -12,7 +12,7 @@ futures = "0.3"
futures-channel = "0.3"
futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] }
log = "0.4"
multi-tcp-client = { path = "../common/clients/multi-tcp-client" }
multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" }
pretty_env_logger = "0.3"
tokio = { version = "0.2", features = ["full"] }
tokio-tungstenite = "0.10.1"
+2 -2
View File
@@ -22,8 +22,8 @@ tokio = { version = "0.2", features = ["full"] }
## internal
config = {path = "../common/config"}
crypto = {path = "../common/crypto"}
directory-client = { path = "../common/clients/directory-client" }
multi-tcp-client = { path = "../common/clients/multi-tcp-client" }
directory-client = { path = "../common/client-libs/directory-client" }
multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" }
nymsphinx = {path = "../common/nymsphinx" }
pemstore = {path = "../common/pemstore"}
topology = {path = "../common/topology"}
+1 -1
View File
@@ -130,7 +130,7 @@ impl MixNode {
if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists() {
error!(
"Our announce-host is identical to one of existing nodes! (its key is {:?}",
"Our announce-host is identical to an existing node's announce-host! (its key is {:?}",
duplicate_node_key
);
return;
-10
View File
@@ -1,10 +0,0 @@
# Nym Client
The Nym Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
It needs to handle communication with 3 types of remote nodes. Here's the development status of each:
- [ ] Directory nodes
- [ ] Mix nodes
- [ ] Validator nodes
-11
View File
@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
</head>
<body>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script src="./bootstrap.js"></script>
</body>
</html>
+2 -2
View File
@@ -27,7 +27,7 @@ hmac = "0.7.1"
## internal
crypto = { path = "../common/crypto" }
config = { path = "../common/config" }
directory-client = { path = "../common/clients/directory-client" }
directory-client = { path = "../common/client-libs/directory-client" }
nymsphinx = { path = "../common/nymsphinx" }
pemstore = { path = "../common/pemstore" }
sfw-provider-requests = { path = "./sfw-provider-requests" }
@@ -36,7 +36,7 @@ topology = { path = "../common/topology"}
# this dependency is due to requiring to know content of loop message. however, mix-client module itself
# is going to be removed or renamed at some point as it no longer servers its purpose of sending traffic to mix network
# and only provides utility functions
mix-client = { path = "../common/clients/mix-client" }
mix-client = { path = "../common/client-libs/mix-client" }
[build-dependencies]
+1 -1
View File
@@ -127,7 +127,7 @@ impl ServiceProvider {
if let Some(duplicate_provider_key) = self.check_if_same_ip_provider_exists() {
error!(
"Our announce-host is identical to one of existing nodes! (its key is {:?}",
"Our announce-host is identical to an existing node's announce-host! (its key is {:?}",
duplicate_provider_key
);
return;
+1 -1
View File
@@ -28,7 +28,7 @@ tokio = { version = "0.2", features = ["full"] }
## internal
crypto = {path = "../common/crypto"}
config = {path = "../common/config"}
directory-client = { path = "../common/clients/directory-client" }
directory-client = { path = "../common/client-libs/directory-client" }
healthcheck = {path = "../common/healthcheck" }
topology = {path = "../common/topology"}