remove veilid-wasm project, too out of date

refactor state updates and formalize a VeilidState object
work on veilid-flutter api
This commit is contained in:
John Smith 2022-01-18 12:33:14 -05:00
parent e39835d51f
commit 1b6864bf22
36 changed files with 512 additions and 3963 deletions

1
Cargo.lock generated
View File

@ -3774,7 +3774,6 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
name = "veilid-cli" name = "veilid-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"async-std", "async-std",
"async-tungstenite 0.8.0", "async-tungstenite 0.8.0",
"bugsalot", "bugsalot",

View File

@ -6,7 +6,7 @@ members = [
"veilid-cli" "veilid-cli"
] ]
exclude = [ "./external/cursive" ] exclude = [ "./external/cursive", "./veilid-flutter" ]
[patch.crates-io] [patch.crates-io]
cursive = { path = "./external/cursive/cursive" } cursive = { path = "./external/cursive/cursive" }

View File

@ -31,7 +31,6 @@ cfg-if = "^1"
capnp = "^0.14" capnp = "^0.14"
capnp-rpc = "^0.14" capnp-rpc = "^0.14"
config = { version = "0.10.1", features = ["yaml"] } config = { version = "0.10.1", features = ["yaml"] }
anyhow = "^1"
bugsalot = "^0.2" bugsalot = "^0.2"
flexi_logger = "0.17" flexi_logger = "0.17"
thiserror = "^1.0" thiserror = "^1.0"

View File

@ -1,11 +1,11 @@
use crate::command_processor::*; use crate::command_processor::*;
use crate::veilid_client_capnp::*; use crate::veilid_client_capnp::*;
use anyhow::*; use veilid_core::xx::*;
use async_std::prelude::*; use async_std::prelude::*;
use capnp::capability::Promise; use capnp::capability::Promise;
use capnp_rpc::{pry, rpc_twoparty_capnp, twoparty, Disconnector, RpcSystem}; use capnp_rpc::{pry, rpc_twoparty_capnp, twoparty, Disconnector, RpcSystem};
use futures::AsyncReadExt; use futures::AsyncReadExt;
use log::*;
use std::cell::RefCell; use std::cell::RefCell;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
@ -21,24 +21,29 @@ impl VeilidClientImpl {
} }
impl veilid_client::Server for VeilidClientImpl { impl veilid_client::Server for VeilidClientImpl {
fn state_changed( fn update(
&mut self, &mut self,
params: veilid_client::StateChangedParams, params: veilid_client::UpdateParams,
_results: veilid_client::StateChangedResults, _results: veilid_client::UpdateResults,
) -> Promise<(), ::capnp::Error> { ) -> Promise<(), ::capnp::Error> {
let changed = pry!(pry!(params.get()).get_changed()); let veilid_update = pry!(pry!(params.get()).get_veilid_update());
if changed.has_attachment() { let which = match veilid_update.which() {
let attachment = pry!(changed.get_attachment()); Ok(v) => v,
let old_state = pry!(attachment.get_old_state()); Err(e) => {
let new_state = pry!(attachment.get_new_state()); panic!("(missing update kind in schema: {:?})", e);
}
};
match which {
veilid_update::Attachment(Ok(attachment)) => {
let state = pry!(attachment.get_state());
trace!( trace!("Attachment: {}", state as u16);
"AttachmentStateChange: old_state={} new_state={}", self.comproc.update_attachment(state);
old_state as u16, }
new_state as u16 _ => {
); panic!("shouldn't get this")
self.comproc.set_attachment_state(new_state); }
} }
Promise::ok(()) Promise::ok(())
@ -83,13 +88,15 @@ impl ClientApiConnection {
} }
} }
async fn handle_connection(&mut self) -> Result<()> { async fn handle_connection(&mut self) -> Result<(), String> {
trace!("ClientApiConnection::handle_connection"); trace!("ClientApiConnection::handle_connection");
let connect_addr = self.inner.borrow().connect_addr.unwrap(); let connect_addr = self.inner.borrow().connect_addr.unwrap();
// Connect the TCP socket // Connect the TCP socket
let stream = async_std::net::TcpStream::connect(connect_addr).await?; let stream = async_std::net::TcpStream::connect(connect_addr)
.await
.map_err(map_to_string)?;
// If it succeed, disable nagle algorithm // If it succeed, disable nagle algorithm
stream.set_nodelay(true)?; stream.set_nodelay(true).map_err(map_to_string)?;
// Create the VAT network // Create the VAT network
let (reader, writer) = stream.split(); let (reader, writer) = stream.split();
@ -134,7 +141,10 @@ impl ClientApiConnection {
} }
// Don't drop the registration // Don't drop the registration
rpc_system.try_join(request.send().promise).await?; rpc_system
.try_join(request.send().promise)
.await
.map_err(map_to_string)?;
// Drop the server and disconnector too (if we still have it) // Drop the server and disconnector too (if we still have it)
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.borrow_mut();
@ -145,80 +155,81 @@ impl ClientApiConnection {
if !disconnect_requested { if !disconnect_requested {
// Connection lost // Connection lost
Err(anyhow!("Connection lost")) Err("Connection lost".to_owned())
} else { } else {
// Connection finished // Connection finished
Ok(()) Ok(())
} }
} }
pub async fn server_attach(&mut self) -> Result<()> { pub async fn server_attach(&mut self) -> Result<(), String> {
trace!("ClientApiConnection::server_attach"); trace!("ClientApiConnection::server_attach");
let server = { let server = {
let inner = self.inner.borrow(); let inner = self.inner.borrow();
inner inner
.server .server
.as_ref() .as_ref()
.ok_or(anyhow!("Not connected, ignoring attach request"))? .ok_or("Not connected, ignoring attach request".to_owned())?
.clone() .clone()
}; };
let request = server.borrow().attach_request(); let request = server.borrow().attach_request();
let response = request.send().promise.await?; let response = request.send().promise.await.map_err(map_to_string)?;
response.get().map(drop).map_err(|e| anyhow!(e)) response.get().map(drop).map_err(map_to_string)
} }
pub async fn server_detach(&mut self) -> Result<()> { pub async fn server_detach(&mut self) -> Result<(), String> {
trace!("ClientApiConnection::server_detach"); trace!("ClientApiConnection::server_detach");
let server = { let server = {
let inner = self.inner.borrow(); let inner = self.inner.borrow();
inner inner
.server .server
.as_ref() .as_ref()
.ok_or(anyhow!("Not connected, ignoring detach request"))? .ok_or("Not connected, ignoring detach request".to_owned())?
.clone() .clone()
}; };
let request = server.borrow().detach_request(); let request = server.borrow().detach_request();
let response = request.send().promise.await?; let response = request.send().promise.await.map_err(map_to_string)?;
response.get().map(drop).map_err(|e| anyhow!(e)) response.get().map(drop).map_err(map_to_string)
} }
pub async fn server_shutdown(&mut self) -> Result<()> { pub async fn server_shutdown(&mut self) -> Result<(), String> {
trace!("ClientApiConnection::server_shutdown"); trace!("ClientApiConnection::server_shutdown");
let server = { let server = {
let inner = self.inner.borrow(); let inner = self.inner.borrow();
inner inner
.server .server
.as_ref() .as_ref()
.ok_or(anyhow!("Not connected, ignoring attach request"))? .ok_or("Not connected, ignoring attach request".to_owned())?
.clone() .clone()
}; };
let request = server.borrow().shutdown_request(); let request = server.borrow().shutdown_request();
let response = request.send().promise.await?; let response = request.send().promise.await.map_err(map_to_string)?;
response.get().map(drop).map_err(|e| anyhow!(e)) response.get().map(drop).map_err(map_to_string)
} }
pub async fn server_debug(&mut self, what: String) -> Result<String> { pub async fn server_debug(&mut self, what: String) -> Result<String, String> {
trace!("ClientApiConnection::server_debug"); trace!("ClientApiConnection::server_debug");
let server = { let server = {
let inner = self.inner.borrow(); let inner = self.inner.borrow();
inner inner
.server .server
.as_ref() .as_ref()
.ok_or(anyhow!("Not connected, ignoring attach request"))? .ok_or("Not connected, ignoring attach request".to_owned())?
.clone() .clone()
}; };
let mut request = server.borrow().debug_request(); let mut request = server.borrow().debug_request();
request.get().set_what(&what); request.get().set_what(&what);
let response = request.send().promise.await?; let response = request.send().promise.await.map_err(map_to_string)?;
response response
.get()? .get()
.map_err(map_to_string)?
.get_output() .get_output()
.map(|o| o.to_owned()) .map(|o| o.to_owned())
.map_err(|e| anyhow!(e)) .map_err(map_to_string)
} }
// Start Client API connection // Start Client API connection
pub async fn connect(&mut self, connect_addr: SocketAddr) -> Result<()> { pub async fn connect(&mut self, connect_addr: SocketAddr) -> Result<(), String> {
trace!("ClientApiConnection::connect"); trace!("ClientApiConnection::connect");
// Save the address to connect to // Save the address to connect to
self.inner.borrow_mut().connect_addr = Some(connect_addr); self.inner.borrow_mut().connect_addr = Some(connect_addr);

View File

@ -273,7 +273,7 @@ debug - send a debugging command to the Veilid server
// called by client_api_connection // called by client_api_connection
// calls into ui // calls into ui
//////////////////////////////////////////// ////////////////////////////////////////////
pub fn set_attachment_state(&mut self, state: AttachmentState) { pub fn update_attachment(&mut self, state: AttachmentState) {
self.inner_mut().ui.set_attachment_state(state); self.inner_mut().ui.set_attachment_state(state);
} }

View File

@ -1,11 +1,11 @@
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(unused_must_use)] #![deny(unused_must_use)]
use anyhow::*; use veilid_core::xx::*;
use async_std::prelude::*; use async_std::prelude::*;
use clap::{App, Arg, ColorChoice}; use clap::{App, Arg, ColorChoice};
use flexi_logger::*; use flexi_logger::*;
use log::*;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
@ -19,7 +19,7 @@ pub mod veilid_client_capnp {
include!(concat!(env!("OUT_DIR"), "/proto/veilid_client_capnp.rs")); include!(concat!(env!("OUT_DIR"), "/proto/veilid_client_capnp.rs"));
} }
fn parse_command_line(default_config_path: &OsStr) -> Result<clap::ArgMatches, anyhow::Error> { fn parse_command_line(default_config_path: &OsStr) -> Result<clap::ArgMatches, String> {
let matches = App::new("veilid-cli") let matches = App::new("veilid-cli")
.version("0.1") .version("0.1")
.color(ColorChoice::Auto) .color(ColorChoice::Auto)
@ -59,7 +59,7 @@ fn parse_command_line(default_config_path: &OsStr) -> Result<clap::ArgMatches, a
} }
#[async_std::main] #[async_std::main]
async fn main() -> Result<()> { async fn main() -> Result<(), String> {
// Get command line options // Get command line options
let default_config_path = settings::Settings::get_default_config_path(); let default_config_path = settings::Settings::get_default_config_path();
let matches = parse_command_line(default_config_path.as_os_str())?; let matches = parse_command_line(default_config_path.as_os_str())?;
@ -73,7 +73,7 @@ async fn main() -> Result<()> {
matches.occurrences_of("config-file") == 0, matches.occurrences_of("config-file") == 0,
matches.value_of_os("config-file").unwrap(), matches.value_of_os("config-file").unwrap(),
) )
.map_err(Box::new)?; .map_err(map_to_string)?;
// Set config from command line // Set config from command line
if matches.occurrences_of("debug") != 0 { if matches.occurrences_of("debug") != 0 {
@ -104,7 +104,8 @@ async fn main() -> Result<()> {
if settings.logging.terminal.enabled { if settings.logging.terminal.enabled {
let flv = sivui.cursive_flexi_logger(); let flv = sivui.cursive_flexi_logger();
if settings.logging.file.enabled { if settings.logging.file.enabled {
std::fs::create_dir_all(settings.logging.file.directory.clone())?; std::fs::create_dir_all(settings.logging.file.directory.clone())
.map_err(map_to_string)?;
logger logger
.log_target(LogTarget::FileAndWriter(flv)) .log_target(LogTarget::FileAndWriter(flv))
.suppress_timestamp() .suppress_timestamp()
@ -121,7 +122,8 @@ async fn main() -> Result<()> {
.expect("failed to initialize logger!"); .expect("failed to initialize logger!");
} }
} else if settings.logging.file.enabled { } else if settings.logging.file.enabled {
std::fs::create_dir_all(settings.logging.file.directory.clone())?; std::fs::create_dir_all(settings.logging.file.directory.clone())
.map_err(map_to_string)?;
logger logger
.log_target(LogTarget::File) .log_target(LogTarget::File)
.suppress_timestamp() .suppress_timestamp()
@ -135,7 +137,7 @@ async fn main() -> Result<()> {
if let Some(address_arg) = matches.value_of("address") { if let Some(address_arg) = matches.value_of("address") {
server_addrs = address_arg server_addrs = address_arg
.to_socket_addrs() .to_socket_addrs()
.context(format!("Invalid server address '{}'", address_arg))? .map_err(|e| format!("Invalid server address '{}'", e))?
.collect() .collect()
} else { } else {
server_addrs = settings.address.addrs.clone(); server_addrs = settings.address.addrs.clone();

View File

@ -367,7 +367,9 @@ impl AttachmentManager {
attachment_machine.state() attachment_machine.state()
} }
pub async fn wait_for_state(&self, state: AttachmentState) { pub async fn wait_for_state(&self, state: AttachmentState, timeout_ms: Option<u32>) -> bool {
let start_time = intf::get_timestamp();
loop { loop {
let (current_state, eventual) = self let (current_state, eventual) = self
.inner .inner
@ -377,9 +379,28 @@ impl AttachmentManager {
if current_state == state { if current_state == state {
break; break;
} }
if eventual.await == state { if let Some(timeout_ms) = timeout_ms {
break; let timeout_time = start_time + (timeout_ms as u64 * 1000);
let cur_time = intf::get_timestamp();
if timeout_time > cur_time {
let timeout_dur_ms = ((timeout_time - cur_time) / 1000) as u32;
if match intf::timeout(timeout_dur_ms, eventual).await {
Ok(v) => v,
Err(_) => return false,
} == state
{
return true;
}
} else {
return false;
}
} else {
if eventual.await == state {
break;
}
} }
} }
true
} }
} }

View File

@ -27,7 +27,7 @@ pub mod xx;
pub use self::attachment_manager::AttachmentState; pub use self::attachment_manager::AttachmentState;
pub use self::veilid_api::*; pub use self::veilid_api::*;
pub use self::veilid_config::*; pub use self::veilid_config::*;
pub use self::veilid_core::{VeilidCore, VeilidCoreSetup, VeilidState, VeilidStateChange}; pub use self::veilid_core::{VeilidCore, VeilidCoreSetup};
pub mod veilid_capnp { pub mod veilid_capnp {
include!(concat!(env!("OUT_DIR"), "/proto/veilid_capnp.rs")); include!(concat!(env!("OUT_DIR"), "/proto/veilid_capnp.rs"));

View File

@ -8,10 +8,10 @@ static LOREM_IPSUM:&[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing
fn setup_veilid_core() -> VeilidCoreSetup { fn setup_veilid_core() -> VeilidCoreSetup {
VeilidCoreSetup { VeilidCoreSetup {
state_change_callback: Arc::new( update_callback: Arc::new(
move |change: VeilidStateChange| -> SystemPinBoxFuture<()> { move |veilid_update: VeilidUpdate| -> SystemPinBoxFuture<()> {
Box::pin(async move { Box::pin(async move {
trace!("state_change_callback: {:?}", change); trace!("update_callback: {:?}", veilid_update);
}) })
}, },
), ),

View File

@ -5,10 +5,10 @@ use crate::*;
fn setup_veilid_core() -> VeilidCoreSetup { fn setup_veilid_core() -> VeilidCoreSetup {
VeilidCoreSetup { VeilidCoreSetup {
state_change_callback: Arc::new( update_callback: Arc::new(
move |change: VeilidStateChange| -> SystemPinBoxFuture<()> { move |veilid_update: VeilidUpdate| -> SystemPinBoxFuture<()> {
Box::pin(async move { Box::pin(async move {
trace!("state_change_callback: {:?}", change); trace!("update_callback: {:?}", veilid_update);
}) })
}, },
), ),

View File

@ -6,10 +6,10 @@ use crate::*;
fn setup_veilid_core() -> VeilidCoreSetup { fn setup_veilid_core() -> VeilidCoreSetup {
VeilidCoreSetup { VeilidCoreSetup {
state_change_callback: Arc::new( update_callback: Arc::new(
move |change: VeilidStateChange| -> SystemPinBoxFuture<()> { move |veilid_update: VeilidUpdate| -> SystemPinBoxFuture<()> {
Box::pin(async move { Box::pin(async move {
trace!("state_change_callback: {:?}", change); trace!("update_callback: {:?}", veilid_update);
}) })
}, },
), ),

View File

@ -145,10 +145,10 @@ cfg_if! {
pub fn setup_veilid_core() -> VeilidCoreSetup { pub fn setup_veilid_core() -> VeilidCoreSetup {
VeilidCoreSetup { VeilidCoreSetup {
state_change_callback: Arc::new( update_callback: Arc::new(
move |change: VeilidStateChange| -> SystemPinBoxFuture<()> { move |veilid_update: VeilidUpdate| -> SystemPinBoxFuture<()> {
Box::pin(async move { Box::pin(async move {
trace!("state_change_callback: {:?}", change); trace!("update_callback: {:?}", veilid_update);
}) })
}, },
), ),

View File

@ -25,7 +25,7 @@ pub async fn test_attach_detach() {
api.attach().await.unwrap(); api.attach().await.unwrap();
intf::sleep(5000).await; intf::sleep(5000).await;
api.detach().await.unwrap(); api.detach().await.unwrap();
api.wait_for_state(VeilidState::Attachment(AttachmentState::Detached)) api.wait_for_update(VeilidUpdate::Attachment(AttachmentState::Detached), None)
.await .await
.unwrap(); .unwrap();
api.shutdown().await; api.shutdown().await;

View File

@ -5,7 +5,7 @@ pub use debug::*;
pub use crate::rpc_processor::InfoAnswer; pub use crate::rpc_processor::InfoAnswer;
use crate::*; use crate::*;
use attachment_manager::AttachmentManager; use attachment_manager::*;
use core::fmt; use core::fmt;
use network_manager::NetworkManager; use network_manager::NetworkManager;
use routing_table::*; use routing_table::*;
@ -106,6 +106,18 @@ macro_rules! parse_error {
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub enum VeilidUpdate {
Attachment(AttachmentState),
}
#[derive(Debug)]
pub struct VeilidState {
pub attachment: AttachmentState,
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
///
#[derive(Clone, Debug, Default, PartialOrd, PartialEq, Eq, Ord)] #[derive(Clone, Debug, Default, PartialOrd, PartialEq, Eq, Ord)]
pub struct NodeId { pub struct NodeId {
pub key: DHTKey, pub key: DHTKey,
@ -1100,12 +1112,13 @@ impl VeilidAPI {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Attach/Detach // Attach/Detach
// issue state changed updates for updating clients // get a full copy of the current state
pub async fn send_state_update(&self) -> Result<(), VeilidAPIError> { pub async fn get_state(&self) -> Result<VeilidState, VeilidAPIError> {
trace!("VeilidCore::send_state_update"); trace!("VeilidCore::get_state");
let attachment_manager = self.attachment_manager()?; let attachment_manager = self.attachment_manager()?;
attachment_manager.send_state_update().await; Ok(VeilidState {
Ok(()) attachment: attachment_manager.get_state(),
})
} }
// connect to the network // connect to the network
@ -1124,12 +1137,17 @@ impl VeilidAPI {
Ok(()) Ok(())
} }
// wait for state change // wait for a matching update
// xxx: should have optional timeout pub async fn wait_for_update(
pub async fn wait_for_state(&self, state: VeilidState) -> Result<(), VeilidAPIError> { &self,
match state { update: VeilidUpdate,
VeilidState::Attachment(cs) => { timeout_ms: Option<u32>,
self.attachment_manager()?.wait_for_state(cs).await; ) -> Result<(), VeilidAPIError> {
match update {
VeilidUpdate::Attachment(cs) => {
self.attachment_manager()?
.wait_for_state(cs, timeout_ms)
.await;
} }
} }
Ok(()) Ok(())

View File

@ -7,27 +7,14 @@ use crate::xx::*;
cfg_if! { cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
pub type StateChangeCallback = Arc<dyn Fn(VeilidStateChange) -> SystemPinBoxFuture<()>>; pub type UpdateCallback = Arc<dyn Fn(VeilidUpdate) -> SystemPinBoxFuture<()>>;
} else { } else {
pub type StateChangeCallback = Arc<dyn Fn(VeilidStateChange) -> SystemPinBoxFuture<()> + Send + Sync>; pub type UpdateCallback = Arc<dyn Fn(VeilidUpdate) -> SystemPinBoxFuture<()> + Send + Sync>;
} }
} }
#[derive(Debug)]
pub enum VeilidStateChange {
Attachment {
old_state: AttachmentState,
new_state: AttachmentState,
},
}
#[derive(Debug)]
pub enum VeilidState {
Attachment(AttachmentState),
}
pub struct VeilidCoreSetup { pub struct VeilidCoreSetup {
pub state_change_callback: StateChangeCallback, pub update_callback: UpdateCallback,
pub config_callback: ConfigCallback, pub config_callback: ConfigCallback,
} }
@ -139,16 +126,13 @@ impl VeilidCore {
// Set up attachment manager // Set up attachment manager
trace!("VeilidCore::internal_startup init attachment manager"); trace!("VeilidCore::internal_startup init attachment manager");
let cb = setup.state_change_callback; let cb = setup.update_callback;
let attachment_manager = let attachment_manager =
AttachmentManager::new(config.clone(), table_store.clone(), crypto.clone()); AttachmentManager::new(config.clone(), table_store.clone(), crypto.clone());
attachment_manager attachment_manager
.init(Arc::new( .init(Arc::new(
move |old_state: AttachmentState, new_state: AttachmentState| { move |_old_state: AttachmentState, new_state: AttachmentState| {
cb(VeilidStateChange::Attachment { cb(VeilidUpdate::Attachment(new_state))
old_state,
new_state,
})
}, },
)) ))
.await?; .await?;

View File

@ -13,6 +13,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter_web_plugins: flutter_web_plugins:
sdk: flutter sdk: flutter
flutter_rust_bridge: ^1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

64
veilid-flutter/rust/.gitignore vendored Normal file
View File

@ -0,0 +1,64 @@
##############################################################################
### MacOS
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
##############################################################################
### Windows
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
###############################################################################
### Rust
target/
logs/

View File

@ -0,0 +1,12 @@
[package]
name = "veilid-flutter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
veilid-core = { path="../../veilid-core" }
flutter_rust_bridge = "^1"

View File

@ -0,0 +1,204 @@
use flutter_rust_bridge::*;
/////////////////////////////////////////
// Config Settings
// Not all settings available through Veilid API are available to Flutter applications
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigUDP {
pub enabled: bool,
pub socket_pool_size: u32,
pub listen_address: String,
pub public_address: Option<String>,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigTCP {
pub connect: bool,
pub listen: bool,
pub max_connections: u32,
pub listen_address: String,
pub public_address: Option<String>,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigWS {
pub connect: bool,
pub listen: bool,
pub max_connections: u32,
pub listen_address: String,
pub path: String,
pub url: Option<String>,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigWSS {
pub connect: bool,
pub max_connections: u32,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigProtocol {
pub udp: VeilidConfigUDP,
pub tcp: VeilidConfigTCP,
pub ws: VeilidConfigWS,
pub wss: VeilidConfigWSS,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigDHT {
pub resolve_node_timeout: Option<u64>,
pub resolve_node_count: u32,
pub resolve_node_fanout: u32,
pub max_find_node_count: u32,
pub get_value_timeout: Option<u64>,
pub get_value_count: u32,
pub get_value_fanout: u32,
pub set_value_timeout: Option<u64>,
pub set_value_count: u32,
pub set_value_fanout: u32,
pub min_peer_count: u32,
pub min_peer_refresh_time: u64,
pub validate_dial_info_receipt_time: u64,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigRPC {
pub concurrency: u32,
pub queue_size: u32,
pub max_timestamp_behind: Option<u64>,
pub max_timestamp_ahead: Option<u64>,
pub timeout: u64,
pub max_route_hop_count: u8,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigLeases {
pub max_server_signal_leases: u32,
pub max_server_relay_leases: u32,
pub max_client_signal_leases: u32,
pub max_client_relay_leases: u32,
}
#[derive(Debug, Default, Clone)]
pub struct VeilidConfigNetwork {
pub max_connections: u32,
pub connection_initial_timeout: u64,
pub node_id: key::DHTKey,
pub node_id_secret: key::DHTKeySecret,
pub bootstrap: Vec<String>,
pub rpc: VeilidConfigRPC,
pub dht: VeilidConfigDHT,
pub upnp: bool,
pub natpmp: bool,
pub enable_local_peer_scope: bool,
pub restricted_nat_retries: u32,
pub tls: VeilidConfigTLS,
pub protocol: VeilidConfigProtocol,
pub leases: VeilidConfigLeases,
}
#[derive(Default, Clone)]
pub struct VeilidConfigTableStore {
pub directory: String,
}
#[derive(Default, Clone)]
pub struct VeilidConfigProtectedStore {
pub allow_insecure_fallback: bool,
pub always_use_insecure_storage: bool,
pub insecure_fallback_directory: String,
}
#[derive(Default, Clone)]
pub struct VeilidConfigCapabilities {
pub protocol_udp: bool,
pub protocol_connect_tcp: bool,
pub protocol_accept_tcp: bool,
pub protocol_connect_ws: bool,
pub protocol_accept_ws: bool,
pub protocol_connect_wss: bool,
pub protocol_accept_wss: bool,
}
#[derive(Default, Clone)]
pub struct VeilidConfig {
pub program_name: String,
pub namespace: String,
pub capabilities: VeilidConfigCapabilities,
pub protected_store: VeilidConfigProtectedStore,
pub table_store: VeilidConfigTableStore,
pub network: VeilidConfigNetwork,
}
/////////////////////////////////////////
/////////////////////////////////////////
#[derive(Debug)]
pub enum APIErrorKind {
AlreadyInitialized,
NotInitialized,
InvalidConfig,
Timeout,
Shutdown,
NodeNotFound(String),
NoDialInfo(String),
Internal(String),
Unimplemented(String),
ParseError {
message: String,
value: String,
},
InvalidArgument {
context: String,
argument: String,
value: String,
},
MissingArgument {
context: String,
argument: String,
},
}
#[derive(Debug)]
pub struct VeilidAPIError {
kind: APIErrorKind,
message: String,
}
#[derive(Debug)]
pub enum AttachmentState {
Detached,
Attaching,
AttachedWeak,
AttachedGood,
AttachedStrong,
FullyAttached,
OverAttached,
Detaching,
}
#[derive(Debug)]
pub enum VeilidUpdate {
Attachment (AttachmentState),
}
/////////////////////////////////////////
///
pub fn startup_veilid_core(sink: StreamSink<VeilidUpdate>, config: VeilidConfig) -> Result<(), VeilidAPIError> {
let core = veilid_core::VeilidCore::new();
core.
}
pub fn get_veilid_state() -> Result<VeilidState, VeilidAPIError> {
}
// xxx api functions
pub fn shutdown_veilid_core() -> Result<(), VeilidAPIError> {
}

View File

@ -0,0 +1,2 @@
mod api;
mod bridge_generated;

View File

@ -11,21 +11,19 @@ enum AttachmentState {
detaching @7; detaching @7;
} }
struct Attachment {
state @0 :AttachmentState;
}
struct VeilidUpdate {
union {
attachment @0 :Attachment;
dummy @1 :Void;
}
}
struct VeilidState { struct VeilidState {
# union { attachment @0 :Attachment;
attachment @0 :AttachmentState;
# }
}
struct AttachmentStateChange {
oldState @0 :AttachmentState;
newState @1 :AttachmentState;
}
struct VeilidStateChange {
# union {
attachment @0 :AttachmentStateChange;
# }
} }
interface Registration {} interface Registration {}
@ -33,18 +31,17 @@ interface Registration {}
interface VeilidServer { interface VeilidServer {
register @0 (veilidClient: VeilidClient) -> (registration: Registration); register @0 (veilidClient: VeilidClient) -> (registration: Registration);
debug @1 (what: Text) -> (output: Text);
attach @1 (); attach @2 ();
detach @2 (); detach @3 ();
shutdown @3 (); shutdown @4 ();
getState @5 () -> (state: VeilidState);
debug @4 (what: Text) -> (output: Text);
} }
interface VeilidClient { interface VeilidClient {
stateChanged @0 (changed: VeilidStateChange); update @0 (veilidUpdate: VeilidUpdate);
logMessage @1 (message: Text); logMessage @1 (message: Text);
} }

View File

@ -17,6 +17,40 @@ use veilid_core::xx::Eventual;
#[fail(display = "Client API error: {}", _0)] #[fail(display = "Client API error: {}", _0)]
pub struct ClientAPIError(String); pub struct ClientAPIError(String);
fn convert_attachment_state(state: &veilid_core::AttachmentState) -> AttachmentState {
match state {
veilid_core::AttachmentState::Detached => AttachmentState::Detached,
veilid_core::AttachmentState::Attaching => AttachmentState::Attaching,
veilid_core::AttachmentState::AttachedWeak => AttachmentState::AttachedWeak,
veilid_core::AttachmentState::AttachedGood => AttachmentState::AttachedGood,
veilid_core::AttachmentState::AttachedStrong => AttachmentState::AttachedStrong,
veilid_core::AttachmentState::FullyAttached => AttachmentState::FullyAttached,
veilid_core::AttachmentState::OverAttached => AttachmentState::OverAttached,
veilid_core::AttachmentState::Detaching => AttachmentState::Detaching,
}
}
fn convert_update(
update: &veilid_core::VeilidUpdate,
rpc_update: crate::veilid_client_capnp::veilid_update::Builder,
) {
match update {
veilid_core::VeilidUpdate::Attachment(state) => {
let mut att = rpc_update.init_attachment();
att.set_state(convert_attachment_state(state));
}
}
}
fn convert_state(
state: &veilid_core::VeilidState,
rpc_state: crate::veilid_client_capnp::veilid_state::Builder,
) {
rpc_state
.init_attachment()
.set_state(convert_attachment_state(&state.attachment));
}
// --- interface Registration --------------------------------- // --- interface Registration ---------------------------------
struct RegistrationHandle { struct RegistrationHandle {
@ -104,13 +138,25 @@ impl veilid_server::Server for VeilidServerImpl {
self.next_id += 1; self.next_id += 1;
// Send state update Promise::ok(())
}
fn debug(
&mut self,
params: veilid_server::DebugParams,
mut results: veilid_server::DebugResults,
) -> Promise<(), ::capnp::Error> {
trace!("VeilidServerImpl::debug");
let veilid_api = self.veilid_api.clone(); let veilid_api = self.veilid_api.clone();
let what = pry!(pry!(params.get()).get_what()).to_owned();
Promise::from_future(async move { Promise::from_future(async move {
veilid_api let output = veilid_api
.send_state_update() .debug(what)
.await .await
.map_err(|e| ::capnp::Error::failed(format!("{:?}", e))) .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))?;
results.get().set_output(output.as_str());
Ok(())
}) })
} }
@ -128,6 +174,7 @@ impl veilid_server::Server for VeilidServerImpl {
.map_err(|e| ::capnp::Error::failed(format!("{:?}", e))) .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))
}) })
} }
fn detach( fn detach(
&mut self, &mut self,
_params: veilid_server::DetachParams, _params: veilid_server::DetachParams,
@ -142,6 +189,7 @@ impl veilid_server::Server for VeilidServerImpl {
.map_err(|e| ::capnp::Error::failed(format!("{:?}", e))) .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))
}) })
} }
fn shutdown( fn shutdown(
&mut self, &mut self,
_params: veilid_server::ShutdownParams, _params: veilid_server::ShutdownParams,
@ -161,21 +209,21 @@ impl veilid_server::Server for VeilidServerImpl {
Promise::ok(()) Promise::ok(())
} }
fn debug( fn get_state(
&mut self, &mut self,
params: veilid_server::DebugParams, _params: veilid_server::GetStateParams,
mut results: veilid_server::DebugResults, mut results: veilid_server::GetStateResults,
) -> Promise<(), ::capnp::Error> { ) -> Promise<(), ::capnp::Error> {
trace!("VeilidServerImpl::debug"); trace!("VeilidServerImpl::get_state");
let veilid_api = self.veilid_api.clone(); let veilid_api = self.veilid_api.clone();
let what = pry!(pry!(params.get()).get_what()).to_owned();
Promise::from_future(async move { Promise::from_future(async move {
let output = veilid_api let state = veilid_api
.debug(what) .get_state()
.await .await
.map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))?; .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))?;
results.get().set_output(output.as_str());
let rpc_state = results.get().init_state();
convert_state(&state, rpc_state);
Ok(()) Ok(())
}) })
} }
@ -260,35 +308,6 @@ impl ClientApi {
incoming_loop.await incoming_loop.await
} }
fn convert_attachment_state(state: &veilid_core::AttachmentState) -> AttachmentState {
match state {
veilid_core::AttachmentState::Detached => AttachmentState::Detached,
veilid_core::AttachmentState::Attaching => AttachmentState::Attaching,
veilid_core::AttachmentState::AttachedWeak => AttachmentState::AttachedWeak,
veilid_core::AttachmentState::AttachedGood => AttachmentState::AttachedGood,
veilid_core::AttachmentState::AttachedStrong => AttachmentState::AttachedStrong,
veilid_core::AttachmentState::FullyAttached => AttachmentState::FullyAttached,
veilid_core::AttachmentState::OverAttached => AttachmentState::OverAttached,
veilid_core::AttachmentState::Detaching => AttachmentState::Detaching,
}
}
fn convert_state_changed(
changed: &veilid_core::VeilidStateChange,
rpc_changed: crate::veilid_client_capnp::veilid_state_change::Builder,
) {
match changed {
veilid_core::VeilidStateChange::Attachment {
old_state,
new_state,
} => {
let mut att = rpc_changed.init_attachment();
att.set_old_state(ClientApi::convert_attachment_state(old_state));
att.set_new_state(ClientApi::convert_attachment_state(new_state));
}
}
}
fn send_request_to_all_clients<F, T>(self: Rc<Self>, request: F) fn send_request_to_all_clients<F, T>(self: Rc<Self>, request: F)
where where
F: Fn(u64, &mut RegistrationHandle) -> ::capnp::capability::RemotePromise<T>, F: Fn(u64, &mut RegistrationHandle) -> ::capnp::capability::RemotePromise<T>,
@ -326,11 +345,11 @@ impl ClientApi {
} }
} }
pub fn handle_state_change(self: Rc<Self>, changed: veilid_core::VeilidStateChange) { pub fn handle_update(self: Rc<Self>, veilid_update: veilid_core::VeilidUpdate) {
self.send_request_to_all_clients(|_id, registration| { self.send_request_to_all_clients(|_id, registration| {
let mut request = registration.client.state_changed_request(); let mut request = registration.client.update_request();
let rpc_changed = request.get().init_changed(); let rpc_veilid_update = request.get().init_veilid_update();
ClientApi::convert_state_changed(&changed, rpc_changed); convert_update(&veilid_update, rpc_veilid_update);
request.send() request.send()
}); });
} }

View File

@ -29,18 +29,18 @@ pub async fn run_veilid_server(settings: Settings, logs: VeilidLogs) -> Result<(
// Create client api state change pipe // Create client api state change pipe
let (sender, receiver): ( let (sender, receiver): (
Sender<veilid_core::VeilidStateChange>, Sender<veilid_core::VeilidUpdate>,
Receiver<veilid_core::VeilidStateChange>, Receiver<veilid_core::VeilidUpdate>,
) = bounded(1); ) = bounded(1);
// Create VeilidCore setup // Create VeilidCore setup
let vcs = veilid_core::VeilidCoreSetup { let vcs = veilid_core::VeilidCoreSetup {
state_change_callback: Arc::new( update_callback: Arc::new(
move |change: veilid_core::VeilidStateChange| -> veilid_core::SystemPinBoxFuture<()> { move |change: veilid_core::VeilidUpdate| -> veilid_core::SystemPinBoxFuture<()> {
let sender = sender.clone(); let sender = sender.clone();
Box::pin(async move { Box::pin(async move {
if sender.send(change).await.is_err() { if sender.send(change).await.is_err() {
error!("error sending state change callback"); error!("error sending veilid update callback");
} }
}) })
}, },
@ -70,10 +70,10 @@ pub async fn run_veilid_server(settings: Settings, logs: VeilidLogs) -> Result<(
drop(settingsr); drop(settingsr);
// Handle state changes on main thread for capnproto rpc // Handle state changes on main thread for capnproto rpc
let state_change_receiver_jh = capi.clone().map(|capi| { let update_receiver_jh = capi.clone().map(|capi| {
async_std::task::spawn_local(async move { async_std::task::spawn_local(async move {
while let Ok(change) = receiver.recv().await { while let Ok(change) = receiver.recv().await {
capi.clone().handle_state_change(change); capi.clone().handle_update(change);
} }
}) })
}); });
@ -147,9 +147,9 @@ pub async fn run_veilid_server(settings: Settings, logs: VeilidLogs) -> Result<(
client_log_channel_closer.close(); client_log_channel_closer.close();
} }
// Wait for state change receiver to exit // Wait for update receiver to exit
if let Some(state_change_receiver_jh) = state_change_receiver_jh { if let Some(update_receiver_jh) = update_receiver_jh {
state_change_receiver_jh.await; update_receiver_jh.await;
} }
// Wait for client api log receiver to exit // Wait for client api log receiver to exit

View File

@ -1,11 +0,0 @@
install:
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -V
- cargo -V
build: false
test_script:
- cargo test --locked

View File

View File

@ -1,5 +0,0 @@
/target
**/*.rs.bk
bin/
pkg/
wasm-pack.log

View File

@ -1,69 +0,0 @@
language: rust
sudo: false
cache: cargo
matrix:
include:
# Builds with wasm-pack.
- rust: beta
env: RUST_BACKTRACE=1
addons:
firefox: latest
chrome: stable
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
script:
- cargo generate --git . --name testing
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
# in any of our parent dirs is problematic.
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- wasm-pack build
- wasm-pack test --chrome --firefox --headless
# Builds on nightly.
- rust: nightly
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
# Builds on beta.
- rust: beta
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
# Note: no enabling the `wee_alloc` feature here because it requires
# nightly for now.

2868
veilid-wasm/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
[package]
name = "veilid-wasm"
version = "0.1.0"
authors = ["John Smith <jsmith@example.com>"]
edition = "2018"
license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "^0"
console_error_panic_hook = "^0"
wee_alloc = "^0"
wasm-logger = "^0"
log = "^0"
veilid-core = { path = "../veilid-core" }
cfg-if = "^1"
wasm-bindgen-futures = "^0"
js-sys = "^0"
[dev-dependencies]
wasm-bindgen-test = "^0"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,25 +0,0 @@
Copyright (c) 2018 John Smith <jsmith@example.com>
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,69 +0,0 @@
<div align="center">
<h1><code>wasm-pack-template</code></h1>
<strong>A template for kick starting a Rust and WebAssembly project using <a href="https://github.com/rustwasm/wasm-pack">wasm-pack</a>.</strong>
<p>
<a href="https://travis-ci.org/rustwasm/wasm-pack-template"><img src="https://img.shields.io/travis/rustwasm/wasm-pack-template.svg?style=flat-square" alt="Build Status" /></a>
</p>
<h3>
<a href="https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html">Tutorial</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## About
[**📚 Read this template tutorial! 📚**][template-docs]
This template is designed for compiling Rust libraries into WebAssembly and
publishing the resulting package to NPM.
Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other
templates and usages of `wasm-pack`.
[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html
[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html
## 🚴 Usage
### 🐑 Use `cargo generate` to Clone this Template
[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
```
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
cd my-project
```
### 🛠️ Build with `wasm-pack build`
```
wasm-pack build
```
### 🔬 Test in Headless Browsers with `wasm-pack test`
```
wasm-pack test --headless --firefox
```
### 🎁 Publish to NPM with `wasm-pack publish`
```
wasm-pack publish
```
## 🔋 Batteries Included
* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
between WebAssembly and JavaScript.
* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
for logging panic messages to the developer console.
* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
for small code size.

View File

@ -1,289 +0,0 @@
use crate::*;
pub use wasm_bindgen_futures::*;
#[wasm_bindgen(js_name = VeilidStateChange)]
pub struct JsVeilidStateChange {
kind: String, // "attachment" => AttachmentState(String)
from: JsValue,
to: JsValue,
}
#[wasm_bindgen(js_name = VeilidState)]
pub struct JsVeilidState {
kind: String, // "attachment" => AttachmentState(String)
state: JsValue,
}
#[wasm_bindgen(js_name = VeilidCore)]
pub struct JsVeilidCore {
core: VeilidCore,
}
#[wasm_bindgen(js_class = VeilidCore)]
impl JsVeilidCore {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
set_panic_hook();
JsVeilidCore {
core: VeilidCore::new(),
}
}
fn value_to_string(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(val.as_string().ok_or(())?))
}
fn value_to_option_string(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
if val.is_null() || val.is_undefined() {
return Ok(Box::new(Option::<String>::None));
}
Ok(Box::new(Some(val.as_string().ok_or(())?)))
}
fn value_to_bool(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(val.is_truthy()))
}
fn value_to_u8(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(f64_try_to_unsigned::<u8>(
val.as_f64().ok_or(())?,
)?))
}
fn value_to_u32(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(f64_try_to_unsigned::<u32>(
val.as_f64().ok_or(())?,
)?))
}
fn value_to_u64(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(f64_try_to_unsigned::<u64>(
val.as_f64().ok_or(())?,
)?))
}
fn value_to_option_u64(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
if val.is_null() || val.is_undefined() {
return Ok(Box::new(Option::<u64>::None));
}
Ok(Box::new(Some(f64_try_to_unsigned::<u64>(
val.as_f64().ok_or(())?,
)?)))
}
fn value_to_dht_key(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(
DHTKey::try_decode(val.as_string().ok_or(())?.as_str()).map_err(drop)?,
))
}
fn value_to_dht_key_secret(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(
DHTKeySecret::try_decode(val.as_string().ok_or(())?.as_str()).map_err(drop)?,
))
}
fn value_to_vec_string(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
let arrval = val.dyn_into::<Array>().map_err(drop)?.to_vec();
let mut out = Vec::<String>::with_capacity(arrval.len());
for v in arrval {
out.push(v.as_string().ok_or(())?);
}
Ok(Box::new(out))
}
fn translate_config_callback(key: &str, val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
match key {
// xxx: lots of missing keys here
"namespace" => Self::value_to_string(val),
"capabilities.protocol_udp" => Self::value_to_bool(val),
"capabilities.protocol_connect_tcp" => Self::value_to_bool(val),
"capabilities.protocol_accept_tcp" => Self::value_to_bool(val),
"capabilities.protocol_connect_ws" => Self::value_to_bool(val),
"capabilities.protocol_accept_ws" => Self::value_to_bool(val),
"capabilities.protocol_connect_wss" => Self::value_to_bool(val),
"capabilities.protocol_accept_wss" => Self::value_to_bool(val),
"table_store.directory" => Self::value_to_string(val),
"protected_store.allow_insecure_fallback" => Self::value_to_bool(val),
"protected_store.always_use_insecure_storage" => Self::value_to_bool(val),
"protected_store.insecure_fallback_directory" => Self::value_to_string(val),
"network.max_connections" => Self::value_to_u32(val),
"network.node_id" => Self::value_to_dht_key(val),
"network.node_id_secret" => Self::value_to_dht_key_secret(val),
"network.bootstrap" => Self::value_to_vec_string(val),
"network.rpc.concurrency" => Self::value_to_u32(val),
"network.rpc.queue_size" => Self::value_to_u32(val),
"network.rpc.max_timestamp_behind" => Self::value_to_option_u64(val),
"network.rpc.max_timestamp_ahead" => Self::value_to_option_u64(val),
"network.rpc.timeout" => Self::value_to_u64(val),
"network.rpc.max_route_hop_count" => Self::value_to_u8(val),
"network.dht.resolve_node_timeout" => Self::value_to_option_u64(val),
"network.dht.resolve_node_count" => Self::value_to_u32(val),
"network.dht.resolve_node_fanout" => Self::value_to_u32(val),
"network.dht.max_find_node_count" => Self::value_to_u32(val),
"network.dht.get_value_timeout" => Self::value_to_option_u64(val),
"network.dht.get_value_count" => Self::value_to_u32(val),
"network.dht.get_value_fanout" => Self::value_to_u32(val),
"network.dht.set_value_timeout" => Self::value_to_option_u64(val),
"network.dht.set_value_count" => Self::value_to_u32(val),
"network.dht.set_value_fanout" => Self::value_to_u32(val),
"network.dht.min_peer_count" => Self::value_to_u32(val),
"network.dht.min_peer_refresh_time" => Self::value_to_u64(val),
"network.dht.validate_dial_info_receipt_time" => Self::value_to_u64(val),
"network.upnp" => Self::value_to_bool(val),
"network.natpmp" => Self::value_to_bool(val),
"network.enable_local_peer_scope" => Self::value_to_bool(val),
"network.restricted_nat_retries" => Self::value_to_u32(val),
"network.tls.certificate_path" => Self::value_to_string(val),
"network.tls.private_key_path" => Self::value_to_string(val),
"network.application.path" => Self::value_to_string(val),
"network.application.https.enabled" => Self::value_to_bool(val),
"network.application.https.listen_address" => Self::value_to_string(val),
"network.application.http.enabled" => Self::value_to_bool(val),
"network.application.http.listen_address" => Self::value_to_string(val),
"network.protocol.udp.enabled" => Self::value_to_bool(val),
"network.protocol.udp.socket_pool_size" => Self::value_to_u32(val),
"network.protocol.udp.listen_address" => Self::value_to_string(val),
"network.protocol.udp.public_address" => Self::value_to_option_string(val),
"network.protocol.tcp.connect" => Self::value_to_bool(val),
"network.protocol.tcp.listen" => Self::value_to_bool(val),
"network.protocol.tcp.max_connections" => Self::value_to_u32(val),
"network.protocol.tcp.listen_address" => Self::value_to_string(val),
"network.protocol.tcp.public_address" => Self::value_to_option_string(val),
"network.protocol.ws.connect" => Self::value_to_bool(val),
"network.protocol.ws.listen" => Self::value_to_bool(val),
"network.protocol.ws.max_connections" => Self::value_to_u32(val),
"network.protocol.ws.listen_address" => Self::value_to_string(val),
"network.protocol.ws.path" => Self::value_to_string(val),
"network.protocol.ws.public_address" => Self::value_to_option_string(val),
"network.protocol.wss.connect" => Self::value_to_bool(val),
"network.protocol.wss.listen" => Self::value_to_bool(val),
"network.protocol.wss.max_connections" => Self::value_to_u32(val),
"network.protocol.wss.listen_address" => Self::value_to_string(val),
"network.protocol.wss.path" => Self::value_to_string(val),
"network.protocol.wss.public_address" => Self::value_to_option_string(val),
_ => return Err(()),
}
}
fn translate_veilid_state(state: JsVeilidState) -> Result<VeilidState, JsValue> {
Ok(match state.kind.as_str() {
"attachment" => {
let state_string = state
.state
.as_string()
.ok_or(JsValue::from_str("state should be a string"))?;
let astate = AttachmentState::try_from(state_string)
.map_err(|e| JsValue::from_str(format!("invalid state: {:?}", e).as_str()))?;
VeilidState::Attachment(astate)
}
_ => return Err(JsValue::from_str("unknown state kind")),
})
}
// xxx rework this for new veilid_api mechanism which should be its own js object now
pub fn startup(
&self,
js_state_change_callback: Function,
js_config_callback: Function,
) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
let vcs = VeilidCoreSetup {
state_change_callback: Arc::new(
move |change: VeilidStateChange| -> SystemPinBoxFuture<()> {
let js_state_change_callback = js_state_change_callback.clone();
Box::pin(async move {
let js_change = match change {
VeilidStateChange::Attachment {
old_state,
new_state,
} => JsVeilidStateChange {
kind: "attachment".to_owned(),
from: JsValue::from_str(old_state.to_string().as_str()),
to: JsValue::from_str(new_state.to_string().as_str()),
},
};
let ret = match Function::call1(
&js_state_change_callback,
&JsValue::UNDEFINED,
&JsValue::from(js_change),
) {
Ok(v) => v,
Err(e) => {
error!("calling state change callback failed: {:?}", e);
return;
}
};
let retp: Promise = match ret.dyn_into() {
Ok(v) => v,
Err(e) => {
error!(
"state change callback did not return a promise: {:?}",
e
);
return;
}
};
match JsFuture::from(retp).await {
Ok(_) => (),
Err(e) => {
error!("state change callback returned an error: {:?}", e);
return;
}
};
})
},
),
config_callback: Arc::new(
move |key: String| -> Result<Box<dyn core::any::Any>, String> {
let val = Function::call1(
&js_config_callback,
&JsValue::UNDEFINED,
&JsValue::from_str(key.as_str()),
)
.map_err(|_| {
format!("Failed to get config from callback for key '{}'", key)
})?;
Self::translate_config_callback(key.as_str(), val)
.map_err(|_| format!("invalid value type for config key '{}'", key))
},
),
};
match core.startup(vcs).await {
Ok(_) => Ok(JsValue::UNDEFINED),
Err(e) => Err(JsValue::from_str(
format!("VeilidCore startup() failed: {}", e.to_string()).as_str(),
)),
}
})
}
pub fn send_state_update(&self) {
self.core.send_state_update();
}
pub fn shutdown(&self) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
core.shutdown().await;
Ok(JsValue::UNDEFINED)
})
}
pub fn attach(&self) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
core.attach();
Ok(JsValue::UNDEFINED)
})
}
pub fn detach(&self) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
core.detach();
Ok(JsValue::UNDEFINED)
})
}
pub fn wait_for_state(&self, state: JsVeilidState) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
let state = Self::translate_veilid_state(state)?;
core.wait_for_state(state).await;
Ok(JsValue::UNDEFINED)
})
}
}

View File

@ -1,25 +0,0 @@
#![cfg(target_arch = "wasm32")]
#![no_std]
#[macro_use]
extern crate alloc;
pub use log::*;
pub use wasm_bindgen::prelude::*;
pub use wasm_bindgen::JsCast;
pub use alloc::boxed::Box;
pub use alloc::string::String;
pub use alloc::sync::Arc;
pub use alloc::vec::Vec;
pub use core::convert::TryFrom;
pub use js_sys::*;
pub use js_veilid_core::*;
pub use utils::*;
pub use veilid_core::dht::key::*;
pub use veilid_core::xx::*;
pub use veilid_core::*;
pub use wasm_logger::*;
mod js_veilid_core;
mod utils;

View File

@ -1,38 +0,0 @@
#![cfg(target_arch = "wasm32")]
use cfg_if::*;
use wasm_bindgen::prelude::*;
//use wasm_bindgen_futures::*;
cfg_if! {
if #[cfg(feature = "wee_alloc")] {
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log(s: &str);
#[wasm_bindgen]
pub fn alert(s: &str);
}
pub fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
pub fn f64_try_to_unsigned<T>(f: f64) -> Result<T,()>
where T: core::convert::TryFrom<u64>
{
let rf = f.floor();
if rf < 0.0 {
return Err(());
}
T::try_from(rf as u64).map_err(drop)
}

View File

@ -1,182 +0,0 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
extern crate alloc;
extern crate wasm_bindgen_test;
use core::sync::atomic::AtomicBool;
use veilid_wasm::*;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
static SETUP_ONCE: AtomicBool = AtomicBool::new(false);
pub fn setup() -> () {
if SETUP_ONCE
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
console_log("setup()");
console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::new(Level::Trace));
init_callbacks();
}
}
// xxx needs updating to new keys and veilid_api object
fn init_callbacks() {
assert_eq!(js_sys::eval(r#"
window.sleep = (milliseconds) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) };
window.stateChangeCallback = async (stateChange) => { console.log("State change: " + stateChange); };
window.configCallback = (configKey) => {
switch(configKey) {
case "namespace": return "";
case "capabilities.protocol_udp": return false;
case "capabilities.protocol_connect_tcp": return false;
case "capabilities.protocol_accept_tcp": return false;
case "capabilities.protocol_connect_ws": return true;
case "capabilities.protocol_accept_ws": return false;
case "capabilities.protocol_connect_wss": return true;
case "capabilities.protocol_accept_wss": return false;
case "table_store.directory": return "";
case "protected_store.allow_insecure_fallback": return true;
case "protected_store.always_use_insecure_storage": return false;
case "protected_store.insecure_fallback_directory": return "";
case "network.max_connections": return 16;
case "network.node_id": return "ZLd4uMYdP4qYLtxF6GqrzBb32Z6T3rE2FWMkWup1pdY";
case "network.node_id_secret": return "s2Gvq6HJOxgQh-3xIgfWSL3I-DWZ2c1RjZLJl2Xmg2E";
case "network.bootstrap": return [];
case "network.rpc.concurrency": return 2;
case "network.rpc.queue_size": return 128;
case "network.rpc.max_timestamp_behind": return 10000000;
case "network.rpc.max_timestamp_ahead": return 10000000;
case "network.rpc.timeout": return 10000000;
case "network.rpc.max_route_hop_count": return 7;
case "network.dht.resolve_node_timeout": return null;
case "network.dht.resolve_node_count": return 20;
case "network.dht.resolve_node_fanout": return 3;
case "network.dht.max_find_node_count": return 20;
case "network.dht.get_value_timeout": return null;
case "network.dht.get_value_count": return 20;
case "network.dht.get_value_fanout": return 3;
case "network.dht.set_value_timeout": return null;
case "network.dht.set_value_count": return 20;
case "network.dht.set_value_fanout": return 5;
case "network.dht.min_peer_count": return 20;
case "network.dht.min_peer_refresh_time": return 2000000;
case "network.dht.validate_dial_info_receipt_time": return 5000000;
case "network.upnp": return false;
case "network.natpmp": return false;
case "network.enable_local_peer_scope": return false;
case "network.restricted_nat_retries": return 3;
case "network.tls.certificate_path": return "";
case "network.tls.private_key_path": return "";
case "network.application.path": return "/app";
case "network.application.https.enabled": return false;
case "network.application.https.listen_address": return "";
case "network.application.http.enabled": return false;
case "network.application.http.listen_address": return "";
case "network.protocol.udp.enabled": return false;
case "network.protocol.udp.socket_pool_size": return 0;
case "network.protocol.udp.listen_address": return "";
case "network.protocol.udp.public_address": return "";
case "network.protocol.tcp.connect": return false;
case "network.protocol.tcp.listen": return false;
case "network.protocol.tcp.max_connections": return 32;
case "network.protocol.tcp.listen_address": return "";
case "network.protocol.tcp.public_address": return "";
case "network.protocol.ws.connect": return true;
case "network.protocol.ws.listen": return false;
case "network.protocol.ws.max_connections": return 16;
case "network.protocol.ws.listen_address": return "";
case "network.protocol.ws.path": return "/ws";
case "network.protocol.ws.public_address": return "";
case "network.protocol.wss.connect": return true;
case "network.protocol.wss.listen": return false;
case "network.protocol.wss.max_connections": return 16;
case "network.protocol.wss.listen_address": return "";
case "network.protocol.wss.path": return "/ws";
case "network.protocol.wss.public_address": return "";
default:
console.log("config key '" + key +"' doesn't exist"); break;
}
};
true
"#).expect("failed to eval"), JsValue::TRUE);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///
#[wasm_bindgen_test]
fn test_construct() {
setup();
assert_eq!(
js_sys::eval(
r#"
let vc = new VeilidCore();
true
"#
)
.expect("failed to eval"),
JsValue::TRUE
);
}
#[wasm_bindgen_test(async)]
async fn test_startup_shutdown() {
setup();
assert_eq!(
JsFuture::from(
js_sys::eval(
r#"
(async function() {
let vc = new VeilidCore();
await vc.startup(window.stateChangeCallback, window.configCallback);
await vc.shutdown();
return true;
})().then(v => {
console.log("finished: " + v);
return v;
});
"#
)
.expect("failed to eval")
.dyn_into::<Promise>()
.unwrap()
)
.await,
Ok(JsValue::TRUE)
);
}
#[wasm_bindgen_test(async)]
async fn test_attach_detach() {
setup();
assert_eq!(
JsFuture::from(
js_sys::eval(
r#"
(async function() {
let vc = new VeilidCore();
await vc.startup(window.stateChangeCallback, window.configCallback);
await vc.attach();
await window.sleep(1000);
await vc.detach();
await vc.shutdown();
return true;
})().then(v => {
console.log("finished: " + v);
return v;
});
"#
)
.expect("failed to eval")
.dyn_into::<Promise>()
.unwrap()
)
.await,
Ok(JsValue::TRUE)
);
}