diff --git a/veilid-flutter/example/macos/Podfile.lock b/veilid-flutter/example/macos/Podfile.lock index b63b0c0e..27a526e1 100644 --- a/veilid-flutter/example/macos/Podfile.lock +++ b/veilid-flutter/example/macos/Podfile.lock @@ -21,7 +21,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 - veilid: 6bed3adec63fd8708a2ace498e0e17941c9fc32b + veilid: f2b3b5b3ac8cd93fc5443ab830d5153575dacf36 PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index 63ca4eea..c3ef7a4f 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -337,30 +337,40 @@ Stream processStreamJson( } } +class _Ctx { + final int id; + final VeilidFFI ffi; + _Ctx(this.id, this.ffi); +} + // FFI implementation of VeilidRoutingContext class VeilidRoutingContextFFI implements VeilidRoutingContext { - final int _id; - final VeilidFFI _ffi; + final _Ctx _ctx; + static final Finalizer<_Ctx> _finalizer = + Finalizer((ctx) => {ctx.ffi._releaseRoutingContext(ctx.id)}); + + VeilidRoutingContextFFI._(this._ctx) { + _finalizer.attach(this, _ctx, detach: this); + } - VeilidRoutingContextFFI._(this._id, this._ffi); @override VeilidRoutingContextFFI withPrivacy() { - final newId = _ffi._routingContextWithPrivacy(_id); - return VeilidRoutingContextFFI._(newId, _ffi); + final newId = _ctx.ffi._routingContextWithPrivacy(_ctx.id); + return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi)); } @override VeilidRoutingContextFFI withCustomPrivacy(Stability stability) { - final newId = _ffi._routingContextWithCustomPrivacy( - _id, stability.json.toNativeUtf8()); - return VeilidRoutingContextFFI._(newId, _ffi); + final newId = _ctx.ffi._routingContextWithCustomPrivacy( + _ctx.id, stability.json.toNativeUtf8()); + return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi)); } @override VeilidRoutingContextFFI withSequencing(Sequencing sequencing) { - final newId = - _ffi._routingContextWithSequencing(_id, sequencing.json.toNativeUtf8()); - return VeilidRoutingContextFFI._(newId, _ffi); + final newId = _ctx.ffi + ._routingContextWithSequencing(_ctx.id, sequencing.json.toNativeUtf8()); + return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi)); } @override @@ -370,8 +380,8 @@ class VeilidRoutingContextFFI implements VeilidRoutingContext { final recvPort = ReceivePort("routing_context_app_call"); final sendPort = recvPort.sendPort; - _ffi._routingContextAppCall( - sendPort.nativePort, _id, nativeEncodedTarget, nativeEncodedRequest); + _ctx.ffi._routingContextAppCall(sendPort.nativePort, _ctx.id, + nativeEncodedTarget, nativeEncodedRequest); final out = await processFuturePlain(recvPort.first); return base64Decode(out); } @@ -381,10 +391,10 @@ class VeilidRoutingContextFFI implements VeilidRoutingContext { var nativeEncodedTarget = target.toNativeUtf8(); var nativeEncodedMessage = base64UrlEncode(message).toNativeUtf8(); - final recvPort = ReceivePort("routing_context_app_call"); + final recvPort = ReceivePort("routing_context_app_message"); final sendPort = recvPort.sendPort; - _ffi._routingContextAppCall( - sendPort.nativePort, _id, nativeEncodedTarget, nativeEncodedMessage); + _ctx.ffi._routingContextAppMessage(sendPort.nativePort, _ctx.id, + nativeEncodedTarget, nativeEncodedMessage); return processFutureVoid(recvPort.first); } } @@ -566,7 +576,7 @@ class VeilidFFI implements Veilid { final sendPort = recvPort.sendPort; _routingContext(sendPort.nativePort); final id = await processFuturePlain(recvPort.first); - return VeilidRoutingContextFFI._(id, this); + return VeilidRoutingContextFFI._(_Ctx(id, this)); } @override diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index 7353ae28..5beff613 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -19,59 +19,62 @@ Future _wrapApiPromise(Object p) { VeilidAPIException.fromJson(jsonDecode(error as String)))); } +class _Ctx { + final int id; + final VeilidJS js; + _Ctx(this.id, this.js); +} + // JS implementation of VeilidRoutingContext class VeilidRoutingContextJS implements VeilidRoutingContext { - final int _id; - final VeilidFFI _ffi; + final _Ctx _ctx; + static final Finalizer<_Ctx> _finalizer = Finalizer((ctx) => { + js_util.callMethod(wasm, "release_routing_context", [ctx.id]) + }); - VeilidRoutingContextFFI._(this._id, this._ffi); - @override - VeilidRoutingContextFFI withPrivacy() { - final newId = _ffi._routingContextWithPrivacy(_id); - return VeilidRoutingContextFFI._(newId, _ffi); + VeilidRoutingContextJS._(this._ctx) { + _finalizer.attach(this, _ctx, detach: this); } @override - VeilidRoutingContextFFI withCustomPrivacy(Stability stability) { - final newId = _ffi._routingContextWithCustomPrivacy( - _id, stability.json.toNativeUtf8()); - return VeilidRoutingContextFFI._(newId, _ffi); + VeilidRoutingContextJS withPrivacy() { + int newId = + js_util.callMethod(wasm, "routing_context_with_privacy", [_ctx.id]); + return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override - VeilidRoutingContextFFI withSequencing(Sequencing sequencing) { - final newId = - _ffi._routingContextWithSequencing(_id, sequencing.json.toNativeUtf8()); - return VeilidRoutingContextFFI._(newId, _ffi); + VeilidRoutingContextJS withCustomPrivacy(Stability stability) { + final newId = js_util.callMethod( + wasm, "routing_context_with_custom_privacy", [_ctx.id, stability.json]); + + return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); + } + + @override + VeilidRoutingContextJS withSequencing(Sequencing sequencing) { + final newId = js_util.callMethod( + wasm, "routing_context_with_sequencing", [_ctx.id, sequencing.json]); + return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override Future appCall(String target, Uint8List request) async { - var nativeEncodedTarget = target.toNativeUtf8(); - var nativeEncodedRequest = base64UrlEncode(request).toNativeUtf8(); + var encodedRequest = base64UrlEncode(request); - final recvPort = ReceivePort("routing_context_app_call"); - final sendPort = recvPort.sendPort; - _ffi._routingContextAppCall( - sendPort.nativePort, _id, nativeEncodedTarget, nativeEncodedRequest); - final out = await processFuturePlain(recvPort.first); - return base64Decode(out); + return base64Decode(await _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_app_call", [_ctx.id, encodedRequest]))); } @override Future appMessage(String target, Uint8List message) async { - var nativeEncodedTarget = target.toNativeUtf8(); - var nativeEncodedMessage = base64UrlEncode(message).toNativeUtf8(); + var encodedMessage = base64UrlEncode(message); - final recvPort = ReceivePort("routing_context_app_call"); - final sendPort = recvPort.sendPort; - _ffi._routingContextAppCall( - sendPort.nativePort, _id, nativeEncodedTarget, nativeEncodedMessage); - return processFutureVoid(recvPort.first); + return _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_app_message", [_ctx.id, encodedMessage])); } } - // JS implementation of high level Veilid API class VeilidJS implements Veilid { @@ -133,30 +136,32 @@ class VeilidJS implements Veilid { js_util.callMethod(wasm, "shutdown_veilid_core", [])); } - @override Future routingContext() async { - final recvPort = ReceivePort("routing_context"); - final sendPort = recvPort.sendPort; - _routingContext(sendPort.nativePort); - final id = await processFuturePlain(recvPort.first); - return VeilidRoutingContextFFI._(id, this); + int id = jsonDecode( + await _wrapApiPromise(js_util.callMethod(wasm, "routing_context", []))); + return VeilidRoutingContextJS._(_Ctx(id, this)); } @override Future newPrivateRoute() async { - final recvPort = ReceivePort("new_private_route"); - final sendPort = recvPort.sendPort; - _newPrivateRoute(sendPort.nativePort); - return processFutureJson(KeyBlob.fromJson, recvPort.first); + Map blobJson = jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "new_private_route", []))); + return KeyBlob.fromJson(blobJson); } @override Future newCustomPrivateRoute( Stability stability, Sequencing sequencing) async { - return _wrapApiPromise( - js_util.callMethod(wasm, "new_custom_private_route", [stability, sequencing])); + var stabilityString = + jsonEncode(stability, toEncodable: veilidApiToEncodable); + var sequencingString = + jsonEncode(sequencing, toEncodable: veilidApiToEncodable); + Map blobJson = jsonDecode(await _wrapApiPromise(js_util + .callMethod( + wasm, "new_private_route", [stabilityString, sequencingString]))); + return KeyBlob.fromJson(blobJson); } @override @@ -178,7 +183,7 @@ class VeilidJS implements Veilid { return _wrapApiPromise( js_util.callMethod(wasm, "app_call_reply", [id, encodedMessage])); } - + @override Future debug(String command) { return _wrapApiPromise(js_util.callMethod(wasm, "debug", [command])); @@ -191,7 +196,7 @@ class VeilidJS implements Veilid { @override VeilidVersion veilidVersion() { - var jsonVersion = + Map jsonVersion = jsonDecode(js_util.callMethod(wasm, "veilid_version", [])); return VeilidVersion( jsonVersion["major"], jsonVersion["minor"], jsonVersion["patch"]); diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 34fec05b..ceab87c0 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -9,6 +9,7 @@ use alloc::sync::Arc; use alloc::*; use core::any::{Any, TypeId}; use core::cell::RefCell; +use core::fmt::Debug; use futures_util::FutureExt; use gloo_utils::format::JsValueSerdeExt; use js_sys::*; @@ -21,6 +22,7 @@ use tracing_subscriber::*; use tracing_wasm::{WASMLayerConfigBuilder, *}; use veilid_core::tools::*; use veilid_core::*; +use wasm_bindgen::prelude::*; use wasm_bindgen_futures::*; // Allocator @@ -57,11 +59,13 @@ fn take_veilid_api() -> Result(val: T) -> JsValue { +pub fn to_json(val: T) -> JsValue { JsValue::from_str(&serialize_json(val)) } -pub fn from_json(val: JsValue) -> Result { +pub fn from_json( + val: JsValue, +) -> Result { let s = val .as_string() .ok_or_else(|| veilid_core::VeilidAPIError::ParseError { @@ -78,7 +82,7 @@ const APIRESULT_UNDEFINED: APIResult<()> = APIResult::Ok(()); pub fn wrap_api_future(future: F) -> Promise where F: Future> + 'static, - T: Serialize + 'static, + T: Serialize + Debug + 'static, { future_to_promise(future.map(|res| { res.map(|v| { @@ -121,7 +125,7 @@ pub struct VeilidWASMConfig { } #[derive(Debug, Deserialize, Serialize)] -pub struct VeilidFFIKeyBlob { +pub struct VeilidKeyBlob { pub key: veilid_core::DHTKey, #[serde(with = "veilid_core::json_as_base64")] pub blob: Vec, @@ -256,17 +260,201 @@ pub fn shutdown_veilid_core() -> Promise { }) } +fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { + let mut next_id: u32 = 1; + let mut rc = (*ROUTING_CONTEXTS).borrow_mut(); + while rc.contains_key(&next_id) { + next_id += 1; + } + rc.insert(next_id, routing_context); + next_id +} + #[wasm_bindgen()] -pub fn debug(command: String) -> Promise { +pub fn routing_context() -> Promise { wrap_api_future(async move { let veilid_api = get_veilid_api()?; - let out = veilid_api.debug(command).await?; - Ok(out) + let routing_context = veilid_api.routing_context(); + let new_id = add_routing_context(routing_context); + APIResult::Ok(new_id) + }) +} + +#[wasm_bindgen()] +pub fn release_routing_context(id: u32) -> i32 { + let mut rc = (*ROUTING_CONTEXTS).borrow_mut(); + if rc.remove(&id).is_none() { + return 0; + } + return 1; +} + +#[wasm_bindgen()] +pub fn routing_context_with_privacy(id: u32) -> u32 { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let Ok(routing_context) = routing_context.clone().with_privacy() else { + return 0; + }; + let new_id = add_routing_context(routing_context); + new_id +} + +#[wasm_bindgen()] +pub fn routing_context_with_custom_privacy(id: u32, stability: String) -> u32 { + let stability: veilid_core::Stability = veilid_core::deserialize_json(&stability).unwrap(); + + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let Ok(routing_context) = routing_context.clone().with_custom_privacy(stability) else { + return 0; + }; + let new_id = add_routing_context(routing_context); + new_id +} + +#[wasm_bindgen()] +pub fn routing_context_with_sequencing(id: u32, sequencing: String) -> u32 { + let sequencing: veilid_core::Sequencing = veilid_core::deserialize_json(&sequencing).unwrap(); + + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let routing_context = routing_context.clone().with_sequencing(sequencing); + let new_id = add_routing_context(routing_context); + new_id +} + +#[wasm_bindgen()] +pub fn routing_context_app_call(id: u32, target: String, request: String) -> Promise { + let request: Vec = data_encoding::BASE64URL_NOPAD + .decode(request.as_bytes()) + .unwrap(); + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + let routing_table = veilid_api.routing_table()?; + let rss = routing_table.route_spec_store(); + + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + }; + routing_context.clone() + }; + + let target: DHTKey = + DHTKey::try_decode(&target).map_err(|e| VeilidAPIError::parse_error(e, &target))?; + + let target = if rss.get_remote_private_route(&target).is_some() { + veilid_core::Target::PrivateRoute(target) + } else { + veilid_core::Target::NodeId(veilid_core::NodeId::new(target)) + }; + + let answer = routing_context.app_call(target, request).await?; + let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); + APIResult::Ok(answer) + }) +} + +#[wasm_bindgen()] +pub fn routing_context_app_message(id: u32, target: String, message: String) -> Promise { + let message: Vec = data_encoding::BASE64URL_NOPAD + .decode(message.as_bytes()) + .unwrap(); + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + let routing_table = veilid_api.routing_table()?; + let rss = routing_table.route_spec_store(); + + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + }; + routing_context.clone() + }; + + let target: DHTKey = + DHTKey::try_decode(&target).map_err(|e| VeilidAPIError::parse_error(e, &target))?; + + let target = if rss.get_remote_private_route(&target).is_some() { + veilid_core::Target::PrivateRoute(target) + } else { + veilid_core::Target::NodeId(veilid_core::NodeId::new(target)) + }; + + routing_context.app_message(target, message).await?; + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn new_private_route() -> Promise { + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + + let (key, blob) = veilid_api.new_private_route().await?; + + let keyblob = VeilidKeyBlob { key, blob }; + + APIResult::Ok(keyblob) + }) +} + +#[wasm_bindgen()] +pub fn new_custom_private_route(stability: String, sequencing: String) -> Promise { + let stability: veilid_core::Stability = veilid_core::deserialize_json(&stability).unwrap(); + let sequencing: veilid_core::Sequencing = veilid_core::deserialize_json(&sequencing).unwrap(); + + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + + let (key, blob) = veilid_api + .new_custom_private_route(stability, sequencing) + .await?; + + let keyblob = VeilidKeyBlob { key, blob }; + + APIResult::Ok(keyblob) + }) +} + +#[wasm_bindgen()] +pub fn import_remote_private_route(blob: String) -> Promise { + let blob: Vec = data_encoding::BASE64URL_NOPAD + .decode(blob.as_bytes()) + .unwrap(); + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + + let key = veilid_api.import_remote_private_route(blob)?; + + APIResult::Ok(key.encode()) + }) +} + +#[wasm_bindgen()] +pub fn release_private_route(key: String) -> Promise { + let key: veilid_core::DHTKey = veilid_core::deserialize_json(&key).unwrap(); + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + veilid_api.release_private_route(&key)?; + APIRESULT_UNDEFINED }) } #[wasm_bindgen()] pub fn app_call_reply(id: String, message: String) -> Promise { + let message: Vec = data_encoding::BASE64URL_NOPAD + .decode(message.as_bytes()) + .unwrap(); wrap_api_future(async move { let id = match id.parse() { Ok(v) => v, @@ -274,15 +462,21 @@ pub fn app_call_reply(id: String, message: String) -> Promise { return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(e, "id", id)) } }; - let message = data_encoding::BASE64URL_NOPAD - .decode(message.as_bytes()) - .map_err(|e| veilid_core::VeilidAPIError::invalid_argument(e, "message", message))?; let veilid_api = get_veilid_api()?; let out = veilid_api.app_call_reply(id, message).await?; Ok(out) }) } +#[wasm_bindgen()] +pub fn debug(command: String) -> Promise { + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + let out = veilid_api.debug(command).await?; + Ok(out) + }) +} + #[wasm_bindgen()] pub fn veilid_version_string() -> String { veilid_core::veilid_version_string()