Skip to main content

rings_core/
utils.rs

1//! Utils for ring-core
2use chrono::Utc;
3/// Get local utc timestamp (millisecond)
4pub fn get_epoch_ms() -> u128 {
5    Utc::now().timestamp_millis() as u128
6}
7
8#[cfg(feature = "wasm")]
9/// Toolset for wasm
10pub mod js_value {
11    use serde::de::DeserializeOwned;
12    use serde::Serialize;
13    use serde::Serializer;
14    use wasm_bindgen::JsValue;
15
16    use crate::error::Error;
17    use crate::error::Result;
18
19    /// From serde to JsValue
20    pub fn serialize(obj: &impl Serialize) -> Result<JsValue> {
21        let serializer = serde_wasm_bindgen::Serializer::json_compatible();
22        serializer
23            .serialize_some(&obj)
24            .map_err(Error::SerdeWasmBindgenError)
25    }
26
27    /// From JsValue to serde
28    pub fn deserialize<T: DeserializeOwned>(obj: impl Into<JsValue>) -> Result<T> {
29        serde_wasm_bindgen::from_value(obj.into()).map_err(Error::SerdeWasmBindgenError)
30    }
31
32    /// From JsValue to serde_json::Value
33    pub fn json_value(obj: impl Into<JsValue>) -> Result<serde_json::Value> {
34        let s = js_sys::JSON::stringify(&obj.into())
35            .map_err(|_| Error::JsError("failed to stringify obj".to_string()))?;
36
37        serde_json::from_str(&String::from(s)).map_err(Error::Deserialize)
38    }
39}
40
41#[cfg(feature = "wasm")]
42pub mod js_func {
43    /// This macro will generate a wrapper for mapping a js_sys::Function with type fn(T, T, T, T) -> Promise<()>
44    /// to native function
45    /// # Example:
46    /// For macro calling: of!(of2, a: T0, b: T1);
47    /// Will generate code:
48    /// ```rust
49    /// pub fn of2<'a, 'b: 'a, T0: TryInto<JsValue> + Clone, T1: TryInto<JsValue> + Clone>(
50    ///     func: &Function,
51    /// ) -> Box<dyn Fn(T0, T1) -> Pin<Box<dyn Future<Output = Result<()>> + 'b>>>
52    /// where
53    ///     T0: 'b,
54    ///     T1: 'b,
55    ///     T0::Error: Debug,
56    ///     T1::Error: Debug,
57    /// {
58    ///     let func = func.clone();
59    ///     Box::new(
60    ///         move |a: T0, b: T1| -> Pin<Box<dyn Future<Output = Result<()>>>> {
61    ///             let func = func.clone();
62    ///             Box::pin(async move {
63    ///                 let func = func.clone();
64    ///                 let params = js_sys::Array::new();
65    ///                 let a: JsValue = a
66    ///                     .clone()
67    ///                     .try_into()
68    ///                     .map_err(|_| Error::JsError(format!("{:?}", e)));
69    ///                 params.push(&a);
70    ///                 let b: JsValue = b
71    ///                     .clone()
72    ///                     .try_into()
73    ///                     .map_err(|_| Error::JsError(format!("{:?}", e)));
74    ///                 params.push(&b);
75    ///                 JsFuture::from(js_sys::Promise::from(
76    ///                     func.apply(&JsValue::NULL, &params).map_err(|e| {
77    ///                         Error::JsError(js_sys::Error::from(e).to_string().into())
78    ///                     })?,
79    ///                 ))
80    ///                 .await
81    ///                 .map_err(|e| Error::JsError(js_sys::Error::from(e).to_string().into()))?;
82    ///                 Ok(())
83    ///             })
84    ///         },
85    ///     )
86    /// }
87    /// ```
88    #[macro_export]
89    macro_rules! of {
90	($func: ident, $($name:ident: $type: ident),+$(,)?) => {
91	    pub fn $func<'a, 'b: 'a, $($type: TryInto<wasm_bindgen::JsValue> + Clone),+>(
92		func: &js_sys::Function,
93	    ) -> Box<dyn Fn($($type),+) -> std::pin::Pin<Box<dyn std::future::Future<Output = $crate::error::Result<()>> + 'b>>>
94	    where  $($type::Error: std::fmt::Debug),+,
95		$($type: 'b),+
96	    {
97		let func = func.clone();
98		Box::new(
99		    move |$($name: $type,)+| -> std::pin::Pin<Box<dyn std::future::Future<Output = $crate::error::Result<()>>>> {
100			let func = func.clone();
101			Box::pin(async move {
102			    let func = func.clone();
103			    let params = js_sys::Array::new();
104			    $(
105				let $name: wasm_bindgen::JsValue = $name.clone().try_into().map_err(|e| $crate::error::Error::JsError(format!("{:?}", e)))?;
106				params.push(&$name);
107			    )+
108			    wasm_bindgen_futures::JsFuture::from(js_sys::Promise::from(
109				func.apply(
110				    &wasm_bindgen::JsValue::NULL,
111				    &params
112				)
113				    .map_err(|e| $crate::error::Error::from(js_sys::Error::from(e)))?,
114			    ))
115				.await
116				.map_err(|e| $crate::error::Error::from(js_sys::Error::from(e)))?;
117			    Ok(())
118			})
119		    },
120		)
121	    }
122	}
123    }
124
125    of!(of1, a: T0);
126    of!(of2, a: T0, b: T1);
127    of!(of3, a: T0, b: T1, c: T2);
128    of!(of4, a: T0, b: T1, c: T2, d: T3);
129}
130
131#[cfg(feature = "wasm")]
132pub mod js_utils {
133    use std::future::Future;
134
135    use wasm_bindgen::closure::Closure;
136    use wasm_bindgen::JsCast;
137    use wasm_bindgen::JsValue;
138
139    pub enum Global {
140        Window(web_sys::Window),
141        WorkerGlobal(web_sys::WorkerGlobalScope),
142        ServiceWorkerGlobal(web_sys::ServiceWorkerGlobalScope),
143    }
144
145    impl Global {
146        pub fn set_timeout_0(
147            &self,
148            callback: &js_sys::Function,
149            millis: i32,
150        ) -> Result<i32, JsValue> {
151            match self {
152                Global::Window(global) => {
153                    global.set_timeout_with_callback_and_timeout_and_arguments_0(callback, millis)
154                }
155                Global::WorkerGlobal(global) => {
156                    global.set_timeout_with_callback_and_timeout_and_arguments_0(callback, millis)
157                }
158                Global::ServiceWorkerGlobal(global) => {
159                    global.set_timeout_with_callback_and_timeout_and_arguments_0(callback, millis)
160                }
161            }
162        }
163    }
164
165    pub fn global() -> Option<Global> {
166        let obj = JsValue::from(js_sys::global());
167        if obj.has_type::<web_sys::Window>() {
168            return Some(Global::Window(web_sys::Window::from(obj)));
169        }
170        if obj.has_type::<web_sys::WorkerGlobalScope>() {
171            return Some(Global::WorkerGlobal(web_sys::WorkerGlobalScope::from(obj)));
172        }
173        if obj.has_type::<web_sys::ServiceWorkerGlobalScope>() {
174            return Some(Global::ServiceWorkerGlobal(
175                web_sys::ServiceWorkerGlobalScope::from(obj),
176            ));
177        }
178        None
179    }
180
181    fn resolve_sleep(resolve: &js_sys::Function) {
182        if let Err(error) = resolve.call0(&JsValue::NULL) {
183            tracing::error!("Failed to resolve sleep promise: {:?}", error);
184        }
185    }
186
187    fn reject_sleep(reject: &js_sys::Function, error: JsValue) {
188        if let Err(reject_error) = reject.call1(&JsValue::NULL, &error) {
189            tracing::error!("Failed to reject sleep promise: {:?}", reject_error);
190        }
191    }
192
193    fn schedule_sleep<F>(resolve: js_sys::Function, reject: js_sys::Function, schedule: F)
194    where F: FnOnce(&js_sys::Function) -> Result<i32, JsValue> {
195        let func = Closure::once_into_js(move || {
196            resolve_sleep(&resolve);
197        });
198        let callback = func.as_ref().unchecked_ref();
199        if let Err(error) = schedule(callback) {
200            tracing::error!("Failed to schedule sleep timeout: {:?}", error);
201            reject_sleep(&reject, error);
202        }
203    }
204
205    pub fn window_sleep(millis: i32) -> wasm_bindgen_futures::JsFuture {
206        let promise = match global() {
207            None => js_sys::Promise::reject(&JsValue::from_str("No global scope for window_sleep")),
208            Some(global) => js_sys::Promise::new(&mut move |resolve, reject| {
209                schedule_sleep(resolve, reject, |callback| {
210                    global.set_timeout_0(callback, millis)
211                });
212            }),
213        };
214        wasm_bindgen_futures::JsFuture::from(promise)
215    }
216
217    /// Spawn a wasm-local interval loop that waits for each tick task to finish.
218    pub fn spawn_interval<F, Fut>(millis: i32, mut task: F)
219    where
220        F: FnMut() -> Fut + 'static,
221        Fut: Future<Output = ()> + 'static,
222    {
223        wasm_bindgen_futures::spawn_local(async move {
224            loop {
225                if let Err(error) = window_sleep(millis).await {
226                    tracing::error!("failed to wait for interval tick: {:?}", error);
227                    return;
228                }
229
230                task().await;
231            }
232        });
233    }
234}