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 ( ( ) )
}