qudag_wasm/
utils.rs

1//! Utility functions for WASM
2//!
3//! Provides helper functions and utilities for WASM operations
4
5use wasm_bindgen::prelude::*;
6use web_sys::console;
7
8/// Set up panic hook for better error messages
9pub fn set_panic_hook() {
10    // When the `console_error_panic_hook` feature is enabled, we can call the
11    // `set_panic_hook` function at least once during initialization, and then
12    // we will get better error messages if our code ever panics.
13    //
14    // For more details see
15    // https://github.com/rustwasm/console_error_panic_hook#readme
16    #[cfg(feature = "console_error_panic_hook")]
17    console_error_panic_hook::set_once();
18}
19
20/// Log a message to the browser console
21#[wasm_bindgen]
22pub fn log(message: &str) {
23    console::log_1(&message.into());
24}
25
26/// Log an error to the browser console
27#[wasm_bindgen]
28pub fn log_error(message: &str) {
29    console::error_1(&message.into());
30}
31
32/// Log a warning to the browser console
33#[wasm_bindgen]
34pub fn log_warn(message: &str) {
35    console::warn_1(&message.into());
36}
37
38/// Performance utilities
39#[wasm_bindgen]
40pub struct Performance;
41
42#[wasm_bindgen]
43impl Performance {
44    /// Get current timestamp in milliseconds
45    #[wasm_bindgen(js_name = "now")]
46    pub fn now() -> f64 {
47        js_sys::Date::now()
48    }
49    
50    /// Measure execution time of a function
51    #[wasm_bindgen(js_name = "measure")]
52    pub fn measure(name: &str, start: f64) -> f64 {
53        let duration = Self::now() - start;
54        log(&format!("{}: {}ms", name, duration));
55        duration
56    }
57}
58
59/// Memory utilities
60#[wasm_bindgen]
61pub struct Memory;
62
63#[wasm_bindgen]
64impl Memory {
65    /// Get current WASM memory usage
66    #[wasm_bindgen(js_name = "getUsage")]
67    pub fn get_usage() -> JsValue {
68        if let Ok(memory) = js_sys::Reflect::get(&wasm_bindgen::memory(), &"buffer".into()) {
69            if let Some(buffer) = memory.dyn_ref::<js_sys::ArrayBuffer>() {
70                let obj = js_sys::Object::new();
71                js_sys::Reflect::set(
72                    &obj,
73                    &"bytes".into(),
74                    &buffer.byte_length().into(),
75                ).unwrap();
76                return obj.into();
77            }
78        }
79        JsValue::NULL
80    }
81}
82
83/// Encoding utilities
84#[wasm_bindgen]
85pub struct Encoding;
86
87#[wasm_bindgen]
88impl Encoding {
89    /// Convert bytes to hex string
90    #[wasm_bindgen(js_name = "bytesToHex")]
91    pub fn bytes_to_hex(bytes: &[u8]) -> String {
92        hex::encode(bytes)
93    }
94    
95    /// Convert hex string to bytes
96    #[wasm_bindgen(js_name = "hexToBytes")]
97    pub fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, JsError> {
98        hex::decode(hex)
99            .map_err(|e| JsError::new(&format!("Invalid hex string: {}", e)))
100    }
101    
102    /// Convert string to bytes (UTF-8)
103    #[wasm_bindgen(js_name = "stringToBytes")]
104    pub fn string_to_bytes(s: &str) -> Vec<u8> {
105        s.as_bytes().to_vec()
106    }
107    
108    /// Convert bytes to string (UTF-8)
109    #[wasm_bindgen(js_name = "bytesToString")]
110    pub fn bytes_to_string(bytes: &[u8]) -> Result<String, JsError> {
111        std::string::String::from_utf8(bytes.to_vec())
112            .map_err(|e| JsError::new(&format!("Invalid UTF-8: {}", e)))
113    }
114}
115
116/// Validation utilities
117#[wasm_bindgen]
118pub struct Validation;
119
120#[wasm_bindgen]
121impl Validation {
122    /// Validate a dark domain name
123    #[wasm_bindgen(js_name = "isDarkDomain")]
124    pub fn is_dark_domain(domain: &str) -> bool {
125        domain.ends_with(".dark") && domain.len() > 5
126    }
127    
128    /// Validate a peer address
129    #[wasm_bindgen(js_name = "isPeerAddress")]
130    pub fn is_peer_address(address: &str) -> bool {
131        // Simple validation - check if it looks like multiaddr
132        address.starts_with("/ip4/") || address.starts_with("/ip6/")
133    }
134    
135    /// Validate a hex string
136    #[wasm_bindgen(js_name = "isValidHex")]
137    pub fn is_valid_hex(hex: &str) -> bool {
138        hex.chars().all(|c| c.is_ascii_hexdigit())
139    }
140}
141
142/// Random utilities
143#[wasm_bindgen]
144pub struct Random;
145
146#[wasm_bindgen]
147impl Random {
148    /// Generate random bytes
149    #[wasm_bindgen(js_name = "getBytes")]
150    pub fn get_bytes(length: usize) -> Result<Vec<u8>, JsError> {
151        let mut bytes = vec![0u8; length];
152        getrandom::getrandom(&mut bytes)
153            .map_err(|e| JsError::new(&format!("Failed to generate random bytes: {}", e)))?;
154        Ok(bytes)
155    }
156    
157    /// Generate a random ID
158    #[wasm_bindgen(js_name = "getId")]
159    pub fn get_id() -> String {
160        let bytes = Self::get_bytes(16).unwrap_or_else(|_| vec![0u8; 16]);
161        hex::encode(bytes)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use wasm_bindgen_test::*;
169    
170    #[wasm_bindgen_test]
171    fn test_encoding_roundtrip() {
172        let original = "Hello, QuDAG!";
173        let bytes = Encoding::string_to_bytes(original);
174        let hex = Encoding::bytes_to_hex(&bytes);
175        let decoded_bytes = Encoding::hex_to_bytes(&hex).unwrap();
176        let decoded = Encoding::bytes_to_string(&decoded_bytes).unwrap();
177        assert_eq!(original, decoded);
178    }
179    
180    #[wasm_bindgen_test]
181    fn test_validation() {
182        assert!(Validation::is_dark_domain("test.dark"));
183        assert!(!Validation::is_dark_domain("test.com"));
184        assert!(Validation::is_peer_address("/ip4/127.0.0.1/tcp/8000"));
185        assert!(Validation::is_valid_hex("deadbeef"));
186        assert!(!Validation::is_valid_hex("not-hex"));
187    }
188}