You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
7.1 KiB
Rust
254 lines
7.1 KiB
Rust
use std::{path::PathBuf, sync::Arc};
|
|
use neon::prelude::*;
|
|
use once_cell::sync::OnceCell;
|
|
use tracing_subscriber::EnvFilter;
|
|
use veilid_core::{CryptoKind, CryptoTyped, KeyPair, CRYPTO_KIND_VLD0, VeilidAPI, api_startup_json, VeilidAPIError};
|
|
|
|
// #[derive(Error, Debug)]
|
|
// pub enum BindingsError {
|
|
// #[error(transparent)]
|
|
// VeilidAPIError (#[from] VeilidAPIError),
|
|
// #[error(transparent)]
|
|
// Throw (#[from] Throw)
|
|
// }
|
|
|
|
fn make_runtime_js<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static tokio::runtime::Runtime> {
|
|
make_runtime().or_else(|error| cx.throw_error(error.to_string()))
|
|
}
|
|
|
|
fn make_runtime() -> Result<&'static tokio::runtime::Runtime, std::io::Error> {
|
|
static RUNTIME: OnceCell<tokio::runtime::Runtime> = OnceCell::new();
|
|
|
|
RUNTIME.get_or_try_init(||
|
|
tokio::runtime::Runtime::new()
|
|
)
|
|
}
|
|
|
|
// fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
|
|
// Ok(cx.string(veilid_core::veilid_version_string()))
|
|
// }
|
|
|
|
// fn async_hello(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
|
// let runtime = make_runtime(&mut cx)?;
|
|
// let (deferred, promise) = cx.promise();
|
|
// let channel = cx.channel();
|
|
|
|
// runtime.spawn(async move {
|
|
// let result = veilid_core::veilid_version_string();
|
|
|
|
// deferred.settle_with(&channel, |mut cx| {
|
|
// Ok(cx.string(result))
|
|
// });
|
|
// });
|
|
|
|
// Ok(promise)
|
|
// }
|
|
|
|
// CryptoKind
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct NoFinalize<V>(V);
|
|
|
|
impl<V> Finalize for NoFinalize<V> {
|
|
fn finalize<'a, C: Context<'a>>(self, _: &mut C) {
|
|
// nothing to do
|
|
}
|
|
}
|
|
|
|
impl<V> NoFinalize<V> {
|
|
fn unbox(self) -> V {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
// trait FunctionContextExt {
|
|
// fn simple_boxed<U: Send>(&mut self, value: U) -> Handle<JsBox<NoFinalize<U>>>;
|
|
// }
|
|
|
|
// impl<'a> FunctionContextExt for FunctionContext<'a> {
|
|
// fn simple_boxed<U: Send>(&mut self, value: U) -> Handle<JsBox<NoFinalize<U>>> {
|
|
// self.boxed(NoFinalize(value))
|
|
// }
|
|
// }
|
|
|
|
type BoxedKeypair = JsBox<NoFinalize<CryptoTyped<KeyPair>>>;
|
|
type BoxedCryptoKind = JsBox<NoFinalize<CryptoKind>>;
|
|
type BoxedPathBuf = JsBox<NoFinalize<PathBuf>>;
|
|
type BoxedLibraryCore = JsBox<LibraryCore>;
|
|
|
|
// Type utility functions
|
|
fn make_path(mut cx: FunctionContext) -> JsResult<BoxedPathBuf> {
|
|
let path_string = cx.argument
|
|
::<JsString>(0)?
|
|
.value(&mut cx);
|
|
|
|
Ok(cx.boxed(NoFinalize(PathBuf::from(path_string))))
|
|
}
|
|
|
|
// Library API
|
|
fn crypto_generate_keypair(mut cx: FunctionContext) -> JsResult<BoxedKeypair> {
|
|
let crypto_kind = cx.argument
|
|
::<BoxedCryptoKind>(0)?
|
|
.unbox();
|
|
|
|
let keypair = veilid_core::Crypto::generate_keypair(crypto_kind)
|
|
.or_else(|err| cx.throw_error(err.to_string()))?;
|
|
|
|
Ok(cx.boxed(NoFinalize(keypair)))
|
|
}
|
|
|
|
fn crypto_format_keypair(mut cx: FunctionContext) -> JsResult<JsObject> {
|
|
let keypair = cx.argument
|
|
::<BoxedKeypair>(0)?
|
|
.unbox();
|
|
|
|
let object = cx.empty_object();
|
|
|
|
let formatted_key = cx.string(format!("{}", keypair.value.key));
|
|
object.set(&mut cx, "key", formatted_key)?;
|
|
|
|
let formatted_secret = cx.string(format!("{}", keypair.value.secret));
|
|
object.set(&mut cx, "secret", formatted_secret)?;
|
|
|
|
Ok(object)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct LibraryCore {
|
|
api: VeilidAPI
|
|
}
|
|
|
|
impl Finalize for LibraryCore {
|
|
fn finalize<'a, C: Context<'a>>(self, _: &mut C) {
|
|
println!("Calling finalize!");
|
|
let runtime = make_runtime().unwrap(); // FIXME: Find better alternative to unwrapping here
|
|
|
|
let _guard = runtime.enter();
|
|
let _ignored_future = self.api.shutdown(); // FIXME: Probably shouldn't ignore this...?
|
|
}
|
|
}
|
|
|
|
impl LibraryCore {
|
|
fn from_json(mut cx: FunctionContext,) -> JsResult<JsPromise> {
|
|
// 0: update_callback
|
|
// 1: json_config
|
|
|
|
let update_callback = cx.argument
|
|
::<JsFunction>(0)?
|
|
.root(&mut cx); // To allow sending to a different thread
|
|
|
|
let update_callback_arc = Arc::new(update_callback);
|
|
|
|
let json_config = cx.argument
|
|
::<JsString>(1)?
|
|
.value(&mut cx);
|
|
|
|
let runtime = make_runtime_js(&mut cx)?;
|
|
let (deferred, promise) = cx.promise();
|
|
let startup_channel = cx.channel();
|
|
let (update_sender, update_receiver) = flume::unbounded();
|
|
|
|
//
|
|
|
|
let _startup_task = runtime.spawn(async move {
|
|
let update_handler = Arc::new(move |update| {
|
|
let result = update_sender.send(update);
|
|
|
|
if let Err(error) = result {
|
|
// FIXME: Don't just dump this to console!
|
|
println!("failed to send veilid update from callback: {:?}", error.into_inner());
|
|
}
|
|
});
|
|
|
|
let core_result = async {
|
|
let api = api_startup_json(update_handler, json_config).await?;
|
|
api.attach().await?;
|
|
let core = LibraryCore { api: api };
|
|
Ok(core)
|
|
}.await;
|
|
|
|
deferred.settle_with(&startup_channel, |mut cx| {
|
|
let core = core_result.or_else(|err: VeilidAPIError| cx.throw_error(err.to_string()))?;
|
|
|
|
let object = cx.empty_object();
|
|
|
|
let _ref = cx.boxed(core);
|
|
object.set(&mut cx, "_ref", _ref)?;
|
|
|
|
let test = JsFunction::new(&mut cx, LibraryCore::test)?;
|
|
object.set(&mut cx, "test", test)?;
|
|
|
|
let test2 = JsFunction::new(&mut cx, LibraryCore::test2)?;
|
|
object.set(&mut cx, "test2", test2)?;
|
|
|
|
Ok(object)
|
|
});
|
|
});
|
|
|
|
let update_channel = cx.channel();
|
|
|
|
let _calling_task = runtime.spawn(async move {
|
|
loop {
|
|
let update = update_receiver.recv_async().await.unwrap(); // FIXME: Not just unwrap
|
|
let update_callback_arc = update_callback_arc.clone();
|
|
|
|
let serialized_update = serde_json::to_string_pretty(&update).unwrap();
|
|
|
|
// The caller should ensure Root::into_inner or Root::drop is called to properly dispose of the Root<T>. If the value is dropped without calling one of these methods:
|
|
|
|
update_channel.send(move |mut cx| {
|
|
update_callback_arc
|
|
.to_inner(&mut cx) // Unpack original callback
|
|
.call_with(&mut cx)
|
|
.arg(cx.string(serialized_update))
|
|
.apply::<JsUndefined, _>(&mut cx)?;
|
|
|
|
Ok(())
|
|
}).join().unwrap(); // FIXME: This will probably block? Also probably shouldn't unwrap here
|
|
}
|
|
});
|
|
|
|
Ok(promise)
|
|
}
|
|
|
|
fn test(mut cx: FunctionContext) -> JsResult<JsString> {
|
|
let this = cx.this()
|
|
.get_value(&mut cx, "_ref")?
|
|
.downcast_or_throw::<BoxedLibraryCore, _>(&mut cx)?;
|
|
|
|
println!("{:?}", this);
|
|
Ok(cx.string(format!("{:?}", &this.api)))
|
|
}
|
|
|
|
fn test2(mut cx: FunctionContext) -> JsResult<JsString> {
|
|
let this = cx.this()
|
|
.get_value(&mut cx, "_ref")?
|
|
.downcast_or_throw::<BoxedLibraryCore, _>(&mut cx)?;
|
|
|
|
println!("{:?}", this);
|
|
Ok(cx.string(format!("{:?}", &this.api)))
|
|
}
|
|
}
|
|
|
|
#[neon::main]
|
|
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
|
// TODO: Make logging optional
|
|
let default_env_filter = EnvFilter::try_from_default_env();
|
|
let fallback_filter = EnvFilter::new("veilid_core=warn,info,debug");
|
|
let env_filter = default_env_filter.unwrap_or(fallback_filter);
|
|
|
|
tracing_subscriber::fmt()
|
|
.with_writer(std::io::stderr)
|
|
.with_env_filter(env_filter)
|
|
.init();
|
|
|
|
cx.export_function("makePath", make_path)?;
|
|
cx.export_function("crypto_formatKeypair", crypto_format_keypair)?;
|
|
cx.export_function("crypto_generateKeypair", crypto_generate_keypair)?;
|
|
cx.export_function("libraryCore_fromJSON", LibraryCore::from_json)?;
|
|
|
|
let crypto_kind_vld0 = cx.boxed(NoFinalize(CRYPTO_KIND_VLD0));
|
|
cx.export_value("CRYPTO_KIND_VLD0", crypto_kind_vld0)?;
|
|
Ok(())
|
|
}
|