1use chrono::Utc;
3pub fn get_epoch_ms() -> u128 {
5 Utc::now().timestamp_millis() as u128
6}
7
8#[cfg(feature = "wasm")]
9pub 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 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 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 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 #[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 ¶ms
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 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}