From e24674642c67245480030ab3eae080e44c849369 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:54:36 +0200 Subject: [PATCH] flake --- flake.lock | 96 +++++++++++++++++++++++++++++++++++++++++++ wasm/src/message.rs | 74 ++++++++++++++++++++++++++++++++- wasm/src/transport.rs | 40 +++++++++++++----- 3 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 flake.lock diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8c32242 --- /dev/null +++ b/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1781577229, + "narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1782357464, + "narHash": "sha256-mXgoT1qDHCdSfF9IvhMtEEFNy9dxrmUfSViwP7RpzOQ=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "77a8263847fb02dc49dbe377278ef6b952f1c6bb", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/wasm/src/message.rs b/wasm/src/message.rs index 0212cd9..52699e8 100644 --- a/wasm/src/message.rs +++ b/wasm/src/message.rs @@ -1,8 +1,9 @@ use wasm_bindgen::prelude::*; use mtp_codec::{ - CommunicationType, CommunicationTypeId, CommunicationValue, DataType, DataTypeId, DataValue, + CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, }; +use mtp_type_map::communication_type_name; use mtp_crypto::{ ChaCha20Poly1305, Ed25519Signer, Keyring, SigAlgorithm, derive_encryption_key, @@ -134,6 +135,77 @@ pub fn parse_auth_response(response: &[u8]) -> Result { Ok(obj.into()) } +/// Build a request frame with the given communication type name, request ID, and JSON data. +/// +/// - `comm_type`: communication type name (e.g. "get_model", "rank_models") or PascalCase +/// - `id`: request ID for response correlation +/// - `json_data`: JSON-stringified request payload +#[wasm_bindgen] +pub fn build_request_frame(comm_type: &str, id: u32, json_data: &str) -> Result, JsValue> { + let comm_type_enum = CommunicationType::from_name(comm_type) + .or_else(|| { + let pascal = comm_type + .split('_') + .map(|s| { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().to_string() + c.as_str(), + } + }) + .collect::(); + CommunicationType::from_name(&pascal) + }) + .ok_or_else(|| js_error(&format!("unknown communication type: {}", comm_type)))?; + + let frame = CommunicationValue::new(comm_type_enum) + .with_id(id) + .add_data(DataTypeId(32), DataValue::Str(json_data.to_string())) + .to_bytes(); + + Ok(frame) +} + +/// Parse a response frame into a JSON string containing `_id`, `_type`, and data fields. +#[wasm_bindgen] +pub fn parse_response_frame(frame: &[u8]) -> Result { + let comm = CommunicationValue::from_bytes(frame) + .map_err(|e| js_error(&format!("parse failed: {}", e)))?; + + let obj = js_sys::Object::new(); + + let _ = js_sys::Reflect::set(&obj, &JsValue::from_str("_id"), &JsValue::from(comm.get_id())); + + let type_name = communication_type_name(comm.get_type().0).unwrap_or("Unknown"); + let _ = js_sys::Reflect::set( + &obj, + &JsValue::from_str("_type"), + &JsValue::from_str(&type_name), + ); + + if let DataValue::Str(s) = comm.get_data(DataTypeId(32)) { + if let Ok(parsed) = js_sys::JSON::parse(s) { + let parsed_obj: &js_sys::Object = parsed.unchecked_ref(); + let entries = js_sys::Object::entries(parsed_obj); + let len = entries.length(); + for i in 0..len { + let entry = js_sys::Array::get(&entries, i); + if let Some(entry_arr) = entry.dyn_ref::() { + if let Some(key) = entry_arr.get(0).as_string() { + let val = entry_arr.get(1); + let _ = js_sys::Reflect::set(&obj, &JsValue::from_str(&key), &val); + } + } + } + } + } + + let stringified = js_sys::JSON::stringify(&obj) + .map_err(|_| js_error("JSON stringify failed"))?; + stringified.as_string() + .ok_or_else(|| js_error("JSON stringify result not a string")) +} + #[cfg(test)] #[cfg(target_arch = "wasm32")] mod tests { diff --git a/wasm/src/transport.rs b/wasm/src/transport.rs index 39fa400..8113256 100644 --- a/wasm/src/transport.rs +++ b/wasm/src/transport.rs @@ -5,6 +5,26 @@ use web_sys::{WebTransport, WebTransportHash, WebTransportOptions}; use crate::error::js_error; +/// Given a `SendStream` (old API with `.writable` or new API where stream IS a WritableStream), +/// return the object to call `.getWriter()` on. +fn resolve_stream_writable(send_stream: &JsValue) -> Result { + let writable = js_sys::Reflect::get(send_stream, &JsValue::from_str("writable")); + match writable { + Ok(val) if !val.is_undefined() && !val.is_null() => Ok(val), + _ => Ok(send_stream.clone()), + } +} + +/// Given a `ReceiveStream` (old API with `.readable` or new API where stream IS a ReadableStream), +/// return the object to call `.getReader()` on. +fn resolve_stream_readable(recv_stream: &JsValue) -> Result { + let readable = js_sys::Reflect::get(recv_stream, &JsValue::from_str("readable")); + match readable { + Ok(val) if !val.is_undefined() && !val.is_null() => Ok(val), + _ => Ok(recv_stream.clone()), + } +} + #[derive(Clone)] pub struct WasmTransport { inner: WebTransport, @@ -50,14 +70,13 @@ impl WasmTransport { let stream_promise = self.inner.create_unidirectional_stream(); let stream = JsFuture::from(stream_promise).await?; - let writable = js_sys::Reflect::get(&stream, &JsValue::from_str("writable")) - .map_err(|_| js_error("missing writable"))?; + let writable_or_stream = resolve_stream_writable(&stream)?; - let writer_val = js_sys::Reflect::get(&writable, &JsValue::from_str("getWriter")) + let writer_val = js_sys::Reflect::get(&writable_or_stream, &JsValue::from_str("getWriter")) .map_err(|_| js_error("missing getWriter"))? .dyn_into::() .map_err(|_| js_error("getWriter not a function"))? - .call0(&writable) + .call0(&writable_or_stream) .map_err(|_| js_error("getWriter call failed"))?; let len = frame.len() as u32; @@ -123,14 +142,13 @@ impl WasmTransport { let recv_stream = js_sys::Reflect::get(&result, &JsValue::from_str("value")) .map_err(|_| js_error("missing value"))?; - let readable = js_sys::Reflect::get(&recv_stream, &JsValue::from_str("readable")) - .map_err(|_| js_error("missing readable"))?; + let readable_or_stream = resolve_stream_readable(&recv_stream)?; - let stream_reader_fn = js_sys::Reflect::get(&readable, &JsValue::from_str("getReader")) + let stream_reader_fn = js_sys::Reflect::get(&readable_or_stream, &JsValue::from_str("getReader")) .map_err(|_| js_error("missing stream getReader"))? .dyn_into::() .map_err(|_| js_error("stream getReader not a function"))?; - let stream_reader = stream_reader_fn.call0(&readable) + let stream_reader = stream_reader_fn.call0(&readable_or_stream) .map_err(|_| js_error("stream getReader call failed"))?; let mut chunks: Vec> = Vec::new(); @@ -228,16 +246,16 @@ impl WasmTransport { Err(_) => continue, }; - let readable = match js_sys::Reflect::get(&recv_stream, &JsValue::from_str("readable")) { + let readable_or_stream = match resolve_stream_readable(&recv_stream) { Ok(v) => v, Err(_) => continue, }; - let stream_reader_fn = match js_sys::Reflect::get(&readable, &JsValue::from_str("getReader")) { + let stream_reader_fn = match js_sys::Reflect::get(&readable_or_stream, &JsValue::from_str("getReader")) { Ok(f) => f.dyn_into::().unwrap(), Err(_) => continue, }; - let stream_reader = match stream_reader_fn.call0(&readable) { + let stream_reader = match stream_reader_fn.call0(&readable_or_stream) { Ok(v) => v, Err(_) => continue, };