exo_wasm/
utils.rs

1//! Utility functions for WASM runtime
2//!
3//! This module provides utility functions for panic handling, logging,
4//! and browser environment detection.
5
6use wasm_bindgen::prelude::*;
7use web_sys::console;
8
9/// Set up panic hook for better error messages in browser console
10pub fn set_panic_hook() {
11    console_error_panic_hook::set_once();
12}
13
14/// Log a message to the browser console
15pub fn log(message: &str) {
16    console::log_1(&JsValue::from_str(message));
17}
18
19/// Log a warning to the browser console
20pub fn warn(message: &str) {
21    console::warn_1(&JsValue::from_str(message));
22}
23
24/// Log an error to the browser console
25pub fn error(message: &str) {
26    console::error_1(&JsValue::from_str(message));
27}
28
29/// Log debug information (includes timing)
30pub fn debug(message: &str) {
31    console::debug_1(&JsValue::from_str(message));
32}
33
34/// Measure execution time of a function
35pub fn measure_time<F, R>(name: &str, f: F) -> R
36where
37    F: FnOnce() -> R,
38{
39    let start = js_sys::Date::now();
40    let result = f();
41    let elapsed = js_sys::Date::now() - start;
42    log(&format!("{} took {:.2}ms", name, elapsed));
43    result
44}
45
46/// Check if running in a Web Worker context
47#[wasm_bindgen]
48pub fn is_web_worker() -> bool {
49    js_sys::eval("typeof WorkerGlobalScope !== 'undefined'")
50        .map(|v| v.is_truthy())
51        .unwrap_or(false)
52}
53
54/// Check if running in a browser with WebAssembly support
55#[wasm_bindgen]
56pub fn is_wasm_supported() -> bool {
57    js_sys::eval("typeof WebAssembly !== 'undefined'")
58        .map(|v| v.is_truthy())
59        .unwrap_or(false)
60}
61
62/// Get browser performance metrics
63#[wasm_bindgen]
64pub fn get_performance_metrics() -> Result<JsValue, JsValue> {
65    let window = web_sys::window().ok_or_else(|| JsValue::from_str("No window object"))?;
66    let performance = window
67        .performance()
68        .ok_or_else(|| JsValue::from_str("No performance object"))?;
69
70    let timing = performance.timing();
71
72    let metrics = serde_json::json!({
73        "navigation_start": timing.navigation_start(),
74        "dom_complete": timing.dom_complete(),
75        "load_event_end": timing.load_event_end(),
76    });
77
78    serde_wasm_bindgen::to_value(&metrics)
79        .map_err(|e| JsValue::from_str(&format!("Failed to serialize metrics: {}", e)))
80}
81
82/// Get available memory (if supported by browser)
83#[wasm_bindgen]
84pub fn get_memory_info() -> Result<JsValue, JsValue> {
85    // Try to access performance.memory (Chrome only)
86    let window = web_sys::window().ok_or_else(|| JsValue::from_str("No window object"))?;
87    let performance = window
88        .performance()
89        .ok_or_else(|| JsValue::from_str("No performance object"))?;
90
91    // This is non-standard and may not be available
92    let result = js_sys::Reflect::get(&performance, &JsValue::from_str("memory"));
93
94    if let Ok(memory) = result {
95        if !memory.is_undefined() {
96            return Ok(memory);
97        }
98    }
99
100    // Fallback: return empty object
101    Ok(js_sys::Object::new().into())
102}
103
104/// Format bytes to human-readable string
105pub fn format_bytes(bytes: f64) -> String {
106    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
107    let mut size = bytes;
108    let mut unit_index = 0;
109
110    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
111        size /= 1024.0;
112        unit_index += 1;
113    }
114
115    format!("{:.2} {}", size, UNITS[unit_index])
116}
117
118/// Generate a random UUID v4
119#[wasm_bindgen]
120pub fn generate_uuid() -> String {
121    // Use crypto.randomUUID if available, otherwise fallback
122    let result = js_sys::eval(
123        "typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : null"
124    );
125
126    if let Ok(uuid) = result {
127        if let Some(uuid_str) = uuid.as_string() {
128            return uuid_str;
129        }
130    }
131
132    // Fallback: simple UUID generation
133    use getrandom::getrandom;
134    let mut bytes = [0u8; 16];
135    if getrandom(&mut bytes).is_ok() {
136        // Set version (4) and variant bits
137        bytes[6] = (bytes[6] & 0x0f) | 0x40;
138        bytes[8] = (bytes[8] & 0x3f) | 0x80;
139
140        format!(
141            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
142            bytes[0], bytes[1], bytes[2], bytes[3],
143            bytes[4], bytes[5],
144            bytes[6], bytes[7],
145            bytes[8], bytes[9],
146            bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]
147        )
148    } else {
149        // Ultimate fallback: timestamp-based ID
150        format!("{}-{}", js_sys::Date::now(), js_sys::Math::random())
151    }
152}
153
154/// Check if localStorage is available
155#[wasm_bindgen]
156pub fn is_local_storage_available() -> bool {
157    js_sys::eval("typeof localStorage !== 'undefined'")
158        .map(|v| v.is_truthy())
159        .unwrap_or(false)
160}
161
162/// Check if IndexedDB is available
163#[wasm_bindgen]
164pub fn is_indexed_db_available() -> bool {
165    js_sys::eval("typeof indexedDB !== 'undefined'")
166        .map(|v| v.is_truthy())
167        .unwrap_or(false)
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_format_bytes() {
176        assert_eq!(format_bytes(100.0), "100.00 B");
177        assert_eq!(format_bytes(1024.0), "1.00 KB");
178        assert_eq!(format_bytes(1024.0 * 1024.0), "1.00 MB");
179    }
180}