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 = "build")]
7mod build;
8#[cfg(feature = "build")]
9pub use build::*;
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::DeserializeOwned as SerdeDeDeserializeOwned;
19#[doc(hidden)]
20pub use serde::de::Error as SerdeDeError;
21#[doc(hidden)]
22pub use serde_json::Error as SerdeJsonError;
23#[doc(hidden)]
24pub use serde_json::Value as SerdeJsonValue;
25#[doc(hidden)]
26pub use serde_json::from_value as serde_json_from_value;
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 =
35    "A function that should return no value instead returned a value";
36// 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
37// be two different versions of dioxus in the graph
38// pub use dioxus::document::eval as dioxus_document_eval;
39// pub use dioxus::document::EvalError as DioxusEvalError;
40
41//************************************************************************//
42
43fn _send_sync_error_assert() {
44    fn is_send<T: Send>(_: &T) {}
45    fn is_sync<T: Sync>(_: &T) {}
46    fn is_error<T: Error>(_: &T) {}
47
48    let o: JsError = JsError::Callback {
49        func: "",
50        callback: "",
51        error: Box::new(std::io::Error::new(std::io::ErrorKind::Other, ""))
52            as Box<dyn Error + Send + Sync>,
53    };
54    is_send(&o);
55    is_sync(&o);
56    is_error(&o);
57}
58
59/// An error related to the execution of a javascript operation
60#[derive(Debug)]
61pub enum JsError {
62    /// Error occurred during dioxus evalution.
63    /// If this occurs, it usually mean your js is not valid or the wrong type was returned from
64    /// your js function
65    Eval {
66        /// The name of the js function
67        func: &'static str,
68        error: dioxus::document::EvalError,
69    },
70    /// A js function that threw a value during execution. The actual error value is logged on the js side as a `console.error`.
71    Threw {
72        /// Name of the js function
73        func: &'static str,
74    },
75    /// Error occurred during a callback to a rust function
76    Callback {
77        /// The name of the function
78        func: &'static str,
79        /// The name of the callback
80        callback: &'static str,
81        /// The error from the callback
82        error: Box<dyn Error + Send + Sync>,
83    },
84}
85
86impl std::fmt::Display for JsError {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        match self {
89            JsError::Eval { func: name, error } => {
90                write!(f, "JavaScript function '{}' eval error: {}", name, error)
91            }
92            JsError::Threw { func: name } => {
93                write!(
94                    f,
95                    "JavaScript function '{}' threw an error during execution",
96                    name
97                )
98            }
99            JsError::Callback {
100                func: name,
101                callback,
102                error,
103            } => {
104                write!(
105                    f,
106                    "JavaScript function '{}' callback '{}' error: {}",
107                    name, callback, error
108                )
109            }
110        }
111    }
112}
113
114impl std::error::Error for JsError {}
115
116//************************************************************************//
117
118/// A reference to a javascript value that can be held on the dioxus side and passed to functions generated
119/// by this crate.
120///
121/// An instance of this is created or used by e.g.
122/// ```rust,ignore
123/// dioxus_use_js::use_js!("ts/example.ts", "assets/example.js"::usingJsValue);
124/// ```
125/// Where `"ts/example.ts"` uses this marker type
126/// ```ts
127/// type JsValue<T = any> = T;
128/// ```
129///
130/// This uses `Arc` internally and the value on the js side is destroyed when the last reference is dropped
131// Dev Note: No `serde::Serialize` or `serde::Deserialize` on purpose since the value is destroyed when dropped
132#[derive(
133    serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash,
134)]
135pub struct JsValue(Arc<Inner>);
136
137/// Abstraction used to implement the one time drop
138#[derive(
139    serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash,
140)]
141struct Inner(String);
142
143impl JsValue {
144    #[deprecated(note = "This constructor is for internal use only. Do not use directly.")]
145    #[doc(hidden)]
146    pub fn internal_create(id: String) -> Self {
147        Self(Arc::new(Inner(id)))
148    }
149
150    #[deprecated(note = "This is for internal use only. Do not use directly.")]
151    #[doc(hidden)]
152    pub fn internal_get(&self) -> &str {
153        self.0.0.as_str()
154    }
155}
156
157impl Display for JsValue {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        write!(f, "Value in js: window[\"{}\"]", self.0.0)
160    }
161}
162
163impl Drop for Inner {
164    fn drop(&mut self) {
165        let object_name = std::mem::take(&mut self.0);
166        // work around for no async drop trait
167        dioxus::core::spawn_forever(async move {
168            let eval = dioxus::document::eval(
169                r#"
170const objectName = await dioxus.recv();
171delete window[objectName];
172return null;
173"#,
174            );
175            if let Err(error) = eval.send(object_name.as_str()) {
176                dioxus::logger::tracing::error!(
177                    "Failed to send object name to clean up `window[\"{object_name}\"]`. Error: {error}"
178                );
179            }
180            if let Err(error) = eval.await {
181                dioxus::logger::tracing::error!(
182                    "Failed to clean up JavaScript object `window[\"{object_name}\"]`. Error: {error}"
183                );
184            } else {
185                dioxus::logger::tracing::trace!(
186                    "Successfully dropped JsValue and cleaned up JavaScript object `window[\"{object_name}\"]`."
187                );
188            }
189        });
190    }
191}