use std::sync::{Mutex, Once};
use wasm_bindgen::prelude::*;
static LAST_PANIC: Mutex<Option<String>> = Mutex::new(None);
static INSTALL: Once = Once::new();
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = error)]
fn console_error(s: &str);
}
pub(crate) fn install_hook() {
INSTALL.call_once(|| {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let msg = info.to_string();
#[cfg(target_arch = "wasm32")]
console_error(&format!("[brepkit] panic: {msg}"));
if let Ok(mut slot) = LAST_PANIC.lock() {
*slot = Some(msg);
}
previous(info);
}));
});
}
#[wasm_bindgen(js_name = "lastPanicMessage")]
#[must_use]
pub fn last_panic_message() -> Option<String> {
LAST_PANIC.lock().ok().and_then(|slot| slot.clone())
}
#[wasm_bindgen(js_name = "clearLastPanicMessage")]
pub fn clear_last_panic_message() {
if let Ok(mut slot) = LAST_PANIC.lock() {
*slot = None;
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
static PANIC_STATE: Mutex<()> = Mutex::new(());
#[test]
fn hook_records_panic_message_and_location() {
let _guard = PANIC_STATE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _kernel = crate::kernel::BrepKernel::new();
clear_last_panic_message();
assert_eq!(last_panic_message(), None);
let caught = std::panic::catch_unwind(|| panic!("panics-module-marker-7391"));
assert!(caught.is_err());
let msg = last_panic_message().expect("hook should have recorded the panic");
assert!(msg.contains("panics-module-marker-7391"), "got: {msg}");
assert!(msg.contains("panics.rs"), "location missing: {msg}");
clear_last_panic_message();
assert_eq!(last_panic_message(), None);
}
}