dioxus_use_js/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4use std::{error::Error, fmt::Display, sync::Arc};
5
6#[cfg(feature = "compile")]
7mod compile;
8#[cfg(feature = "compile")]
9pub use compile::*;
10
11pub use dioxus_use_js_macro::use_js;
12
13// We export these so downstreams don't need `serde` or `serde_json` directly
14// exports used by macro.
15#[doc(hidden)]
16pub use serde::Serialize as SerdeSerialize;
17#[doc(hidden)]
18pub use serde::de::Error as SerdeDeError;
19#[doc(hidden)]
20pub use serde::de::DeserializeOwned as SerdeDeDeserializeOwned;
21#[doc(hidden)]
22pub use serde_json::Value as SerdeJsonValue;
23#[doc(hidden)]
24pub use serde_json::from_value as serde_json_from_value;
25#[doc(hidden)]
26pub use serde_json::Error as SerdeJsonError;
27#[doc(hidden)]
28pub const __SEND_VALIDATION_MSG: &str = "Should always send back a value that is an array of two.";
29#[doc(hidden)]
30pub const __INDEX_VALIDATION_MSG: &str = "The first sent back value should always be a u64.";
31#[doc(hidden)]
32pub const __BAD_CALL_MSG: &str = "Should only attempt to call known actions.";
33#[doc(hidden)]
34pub const __BAD_VOID_RETURN: &str = "A function that should return no value instead returned a value";
35// We do not export this so the dioxus version doing the eval is the same, otherwise it may compile but using two different versions of dioxus at runtime will likely cause a runtime error
36// be two different versions of dioxus in the graph
37// pub use dioxus::document::eval as dioxus_document_eval;
38// pub use dioxus::document::EvalError as DioxusEvalError;
39
40//************************************************************************//
41
42fn _send_sync_error_assert() {
43    fn is_send<T: Send>(_: &T) {}
44    fn is_sync<T: Sync>(_: &T) {}
45    fn is_error<T: Error>(_: &T) {}
46
47    let o: JsError =
48        JsError::Callback(Box::new(std::io::Error::new(std::io::ErrorKind::Other, ""))
49            as Box<dyn Error + Send + Sync>);
50    is_send(&o);
51    is_sync(&o);
52    is_error(&o);
53}
54
55/// An error related to the execution of a javascript operation
56#[derive(Debug)]
57pub enum JsError {
58    /// Error occurred during evaluation. Including serialization/deserialization, communication, and js execution
59    Eval(dioxus::document::EvalError),
60    /// Error occurred during a callback to a rust function
61    Callback(Box<dyn Error + Send + Sync>),
62}
63
64impl std::fmt::Display for JsError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            JsError::Eval(e) => write!(f, "JavaScript error: {}", e),
68            JsError::Callback(error) => write!(f, "Callback error: {}", error),
69        }
70    }
71}
72
73impl std::error::Error for JsError {}
74
75//************************************************************************//
76
77/// A reference to a javascript value that can be held on the dioxus side and passed to functions generated
78/// by this crate.
79///
80/// An instance of this is created or used by e.g.
81/// ```rust,ignore
82/// dioxus_use_js::use_js!("ts/example.ts", "assets/example.js"::usingJsValue);
83/// ```
84/// Where `"ts/example.ts"` uses this marker type
85/// ```ts
86/// type JsValue<T = any> = T;
87/// ```
88///
89/// This uses `Arc` internally and the value on the js side is destroyed when the last reference is dropped
90// Dev Note: No `serde::Serialize` or `serde::Deserialize` on purpose since the value is destroyed when dropped
91#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
92pub struct JsValue(Arc<Inner>);
93
94/// Abstraction used to implement the one time drop
95#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
96struct Inner(String);
97
98impl JsValue {
99    #[deprecated(note = "This constructor is for internal use only. Do not use directly.")]
100    #[doc(hidden)]
101    pub fn internal_create(id: String) -> Self {
102        Self(Arc::new(Inner(id)))
103    }
104
105    #[deprecated(note = "This is for internal use only. Do not use directly.")]
106    #[doc(hidden)]
107    pub fn internal_get(&self) -> &str {
108        self.0.0.as_str()
109    }
110}
111
112impl Display for JsValue {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        write!(f, "Value in js: window[\"{}\"]", self.0.0)
115    }
116}
117
118impl Drop for Inner {
119    fn drop(&mut self) {
120        let object_name = std::mem::take(&mut self.0);
121        // work around for no async drop trait
122        dioxus::core::spawn_forever(async move {
123            let eval = dioxus::document::eval(
124                r#"
125const objectName = await dioxus.recv();
126if (window.hasOwnProperty(objectName)) {
127    delete window[objectName];
128}
129return null;
130"#,
131            );
132            if let Err(error) = eval.send(object_name.as_str()) {
133                dioxus::logger::tracing::error!(
134                    "Failed to send object name to clean up `window[\"{object_name}\"]`. Error: {error}"
135                );
136            }
137            if let Err(error) = eval.await {
138                dioxus::logger::tracing::error!(
139                    "Failed to clean up JavaScript object `window[\"{object_name}\"]`. Error: {error}"
140                );
141            } else {
142                dioxus::logger::tracing::trace!(
143                    "Successfully dropped JsValue and cleaned up JavaScript object `window[\"{object_name}\"]`."
144                );
145            }
146        });
147    }
148}