diff --git a/packages_rs/nextclade-web/Cargo.toml b/packages_rs/nextclade-web/Cargo.toml index 08f8b8da2..31a38b3dc 100644 --- a/packages_rs/nextclade-web/Cargo.toml +++ b/packages_rs/nextclade-web/Cargo.toml @@ -17,7 +17,7 @@ console_error_panic_hook = "=0.1.7" eyre = "=0.6.8" getrandom = { version = "=0.2.10", features = ["js"] } itertools = "=0.11.0" -js-sys = { version = "=0.3.64", features = [] } +js-sys = "=0.3.64" log = "=0.4.19" nextclade = { path = "../nextclade" } schemars = { version = "=0.8.12", features = ["chrono", "either", "enumset", "indexmap1"] } @@ -25,7 +25,7 @@ serde = { version = "=1.0.164", features = ["derive"] } serde-wasm-bindgen = { version = "=0.5.0" } wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } wasm-logger = "=0.2.0" -web-sys = { version = "=0.3.64", features = ["console"] } +web-sys = { version = "=0.3.64", features = ["console", "Window", "Event", "CustomEvent", "Worker", "WorkerGlobalScope", "WorkerNavigator", "WorkerOptions", "WorkerLocation"] } [build-dependencies] nextclade = { path = "../nextclade" } diff --git a/packages_rs/nextclade-web/src/lib.rs b/packages_rs/nextclade-web/src/lib.rs index c2c9a50cb..9e5325a2b 100644 --- a/packages_rs/nextclade-web/src/lib.rs +++ b/packages_rs/nextclade-web/src/lib.rs @@ -1,9 +1,10 @@ +use crate::wasm::panic_hook; use wasm_bindgen::prelude::wasm_bindgen; #[wasm_bindgen(start)] pub fn main() { wasm_logger::init(wasm_logger::Config::default()); - console_error_panic_hook::set_once(); + panic_hook::set_once(); } mod wasm; diff --git a/packages_rs/nextclade-web/src/wasm/mod.rs b/packages_rs/nextclade-web/src/wasm/mod.rs index b5f7dee46..cb5c1ee7e 100644 --- a/packages_rs/nextclade-web/src/wasm/mod.rs +++ b/packages_rs/nextclade-web/src/wasm/mod.rs @@ -1,3 +1,4 @@ pub mod jserr; pub mod main; +pub mod panic_hook; pub mod seq_autodetect; diff --git a/packages_rs/nextclade-web/src/wasm/panic_hook.rs b/packages_rs/nextclade-web/src/wasm/panic_hook.rs new file mode 100644 index 000000000..b854e51a1 --- /dev/null +++ b/packages_rs/nextclade-web/src/wasm/panic_hook.rs @@ -0,0 +1,119 @@ +use eyre::Report; +/// Adapted from https://github.com/rustwasm/console_error_panic_hook +use log; +use schemars::_private::NoSerialize; +use std::panic::{set_hook, PanicInfo}; +use std::sync::Once; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; +use web_sys::Event; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn error(msg: String); + + type Error; + + #[wasm_bindgen(constructor)] + fn new() -> Error; + + #[wasm_bindgen(structural, method, getter)] + fn stack(error: &Error) -> String; +} + +fn hook_impl(info: &PanicInfo) { + let mut msg = info.to_string(); + + // Add the error stack to our message. + // + // This ensures that even if the `console` implementation doesn't + // include stacks for `console.error`, the stack is still available + // for the user. Additionally, Firefox's console tries to clean up + // stack traces, and ruins Rust symbols in the process + // (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since + // it only touches the logged message's associated stack, and not + // the message's contents, by including the stack in the message + // contents we make sure it is available to the user. + msg.push_str("\n\nStack:\n\n"); + let e = Error::new(); + let stack = e.stack(); + msg.push_str(&stack); + + // Safari's devtools, on the other hand, _do_ mess with logged + // messages' contents, so we attempt to break their heuristics for + // doing that by appending some whitespace. + // https://github.com/rustwasm/console_error_panic_hook/issues/7 + msg.push_str("\n\n"); + + js_sys::eval( + // language=javascript + r#" + 'use strict'; + + (function() { + throw new Error('<<<<<<<<<<<<<<< HELLOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO >>>>>>>>>>>>>>>') + })() + "#, + ) + .map_err(|err| { + log::error!("js_sys::eval failed: {err:#?}"); + }) + .ok(); + + send_error_event().unwrap(); + + // Finally, log the panic with `console.error`! + // log::error!("{}", &msg); + // error(msg); + + // wasm_bindgen::throw_val(JsValue::from_str("Hello")); + + // wasm_bindgen::throw_val(JsValue::from(JsError::new(&msg))); + + // js_sys::Function::new_no_args("{\ + // + // \ + // "); + // + // JsError::new("message") +} + +fn send_error_event() -> Result<(), JsValue> { + let global = js_sys::global(); + if global.is_null() { + log::info!("{}", global.to_string()); + } else { + log::info!("no global"); + } + + if let Some(window) = web_sys::window() { + log::info!("window"); + if !window.dispatch_event(&Event::new("error")?)? { + log::error!("Unable to dispatch error event"); + } + } else { + log::info!("no window"); + } + Ok(()) +} + +/// A panic hook for use with +/// [`std::panic::set_hook`](https://doc.rust-lang.org/nightly/std/panic/fn.set_hook.html) +/// that logs panics into +/// [`console.error`](https://developer.mozilla.org/en-US/docs/Web/API/Console/error). +/// +/// On non-wasm targets, prints the panic to `stderr`. +pub fn hook(info: &PanicInfo) { + hook_impl(info); +} + +/// Set the `console.error` panic hook the first time this is called. Subsequent +/// invocations do nothing. +#[inline] +pub fn set_once() { + static SET_HOOK: Once = Once::new(); + SET_HOOK.call_once(|| { + set_hook(Box::new(hook)); + }); +} diff --git a/packages_rs/nextclade-web/src/workers/launcher.worker.ts b/packages_rs/nextclade-web/src/workers/launcher.worker.ts index 0605bd834..ff3714b9e 100644 --- a/packages_rs/nextclade-web/src/workers/launcher.worker.ts +++ b/packages_rs/nextclade-web/src/workers/launcher.worker.ts @@ -94,6 +94,12 @@ class LauncherWorkerImpl { } private onError(error: unknown) { + console.log('onError', { this: this, error }) + + if (!this) { + return + } + this.analysisGlobalStatusObservable.next(AlgorithmGlobalStatus.failed) this.analysisResultsObservable.error(sanitizeError(error)) void this.destroy() // eslint-disable-line no-void