tauri_wasm/
event.rs

1//! Types of tauri [event system].
2//!
3//! [event system]: https://v2.tauri.app/develop/calling-rust/#event-system
4
5use {
6    crate::{error::Error, ext, invoke::Options, string::ToStringValue},
7    js_sys::{JsString, Promise},
8    serde::Serialize,
9    std::{
10        pin::Pin,
11        task::{Context, Poll},
12    },
13    wasm_bindgen::prelude::*,
14    wasm_bindgen_futures::JsFuture,
15};
16
17#[rustfmt::skip]
18#[wasm_bindgen]
19extern "C" {
20    #[wasm_bindgen(thread_local_v2, static_string)]
21    static EMIT: JsString = "plugin:event|emit";
22
23     #[wasm_bindgen(thread_local_v2, static_string)]
24    static EMIT_TO: JsString = "plugin:event|emit_to";
25}
26
27pub(crate) mod api {
28    use super::*;
29
30    /// Sends an [event] to the backend.
31    ///
32    /// [event]: https://v2.tauri.app/develop/calling-rust/#event-system
33    ///
34    /// # Example
35    ///
36    /// Send an event with string payload.
37    ///
38    #[cfg_attr(feature = "serde", doc = "```")]
39    #[cfg_attr(not(feature = "serde"), doc = "```ignore")]
40    /// # async fn e() -> Result<(), tauri_wasm::Error> {
41    /// tauri_wasm::emit("file-selected", "/path/to/file")?.await?;
42    /// # Ok(())
43    /// # }
44    /// ```
45    ///
46    /// You can send any [serializable](Serialize) payload.
47    ///
48    #[cfg_attr(feature = "serde", doc = "```")]
49    #[cfg_attr(not(feature = "serde"), doc = "```ignore")]
50    /// # async fn e() -> Result<(), tauri_wasm::Error> {
51    /// use serde::Serialize;
52    ///
53    /// #[derive(Serialize)]
54    /// struct Message {
55    ///     key: &'static str,
56    ///     data: u32,
57    /// }
58    ///
59    /// let message = Message {
60    ///     key: "secret",
61    ///     data: 37,
62    /// };
63    ///
64    /// tauri_wasm::emit("file-selected", &message)?.await?;
65    /// # Ok(())
66    /// # }
67    /// ```
68    ///
69    /// To trigger an event to a listener registered by a specific target
70    /// you can use the [`to`](Emit::to) function.
71    ///
72    /// # Capabilities
73    ///
74    /// Note that in order to emit events, the Tauri framework
75    /// requires the corresponding capabilities to be enabled.
76    /// For example, let's say our application and its window
77    /// are named "app". Then your `Tauri.toml` config should
78    /// include something like:
79    ///
80    /// ```toml
81    /// [app]
82    /// # app configs..
83    ///
84    /// [[app.security.capabilities]]
85    /// identifier = "default"
86    /// windows = ["app"]
87    /// permissions = ["core:event:default"]
88    /// ```
89    #[inline]
90    pub fn emit<E, P>(event: E, payload: &P) -> Result<Emit<E::Js>, Error>
91    where
92        E: ToStringValue,
93        P: Serialize + ?Sized,
94    {
95        let event = event.to_string_value();
96        let payload =
97            serde_wasm_bindgen::to_value(&payload).map_err(|e| Error(JsValue::from(e)))?;
98        let target = None;
99
100        Ok(Emit {
101            event,
102            payload,
103            target,
104        })
105    }
106}
107
108/// A type used to configure an [emit](api::emit) operation.
109pub struct Emit<E, T = JsValue> {
110    event: E,
111    payload: JsValue,
112    target: Option<EventTarget<T>>,
113}
114
115impl<E> Emit<E> {
116    /// Sends an [event] to a listener registered by a specific target.
117    ///
118    /// [event]: https://v2.tauri.app/develop/calling-rust/#event-system
119    ///
120    /// # Example
121    ///
122    /// Send an event with string payload to the target with "editor" label.
123    ///
124    #[cfg_attr(feature = "serde", doc = "```")]
125    #[cfg_attr(not(feature = "serde"), doc = "```ignore")]
126    /// # async fn e() -> Result<(), tauri_wasm::Error> {
127    /// use tauri_wasm::event::EventTarget;
128    ///
129    /// let target = EventTarget::from("editor");
130    /// tauri_wasm::emit("file-selected", "/path/to/file")?.to(target).await?;
131    /// # Ok(())
132    /// # }
133    /// ```
134    #[inline]
135    pub fn to<S>(self, target: EventTarget<S>) -> Emit<E, S::Js>
136    where
137        S: ToStringValue,
138    {
139        let event = self.event;
140        let payload = self.payload;
141        let target = Some(target.map(|s| s.to_string_value()));
142
143        Emit {
144            event,
145            payload,
146            target,
147        }
148    }
149}
150
151/// Represents the future of an [emit](api::emit) operation.
152pub struct EmitFuture(JsFuture);
153
154impl EmitFuture {
155    /// Returns the inner future.
156    #[inline]
157    pub fn into_future(self) -> JsFuture {
158        self.0
159    }
160}
161
162impl Future for EmitFuture {
163    type Output = Result<JsValue, Error>;
164
165    #[inline]
166    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
167        let me = self.get_mut();
168        Pin::new(&mut me.0).poll(cx).map_err(Error)
169    }
170}
171
172impl<E, T> IntoFuture for Emit<E, T>
173where
174    E: AsRef<JsValue>,
175    T: AsRef<JsValue>,
176{
177    type Output = Result<JsValue, Error>;
178    type IntoFuture = EmitFuture;
179
180    #[inline]
181    fn into_future(self) -> Self::IntoFuture {
182        let target = self.target.as_ref().map(|s| s.as_ref().map(|s| s.as_ref()));
183        let promise = invoke_emit(target, self.event.as_ref(), &self.payload);
184        EmitFuture(JsFuture::from(promise))
185    }
186}
187
188fn invoke_emit(
189    target: Option<EventTarget<&JsValue>>,
190    event: &JsValue,
191    payload: &JsValue,
192) -> Promise {
193    let cmd = if target.is_none() { &EMIT } else { &EMIT_TO };
194
195    let (kind, label) = match target {
196        None => (0, &JsValue::UNDEFINED),
197        Some(target) => match target {
198            EventTarget::Any => (1, &JsValue::UNDEFINED),
199            EventTarget::AnyLabel(s) => (2, s),
200            EventTarget::App => (3, &JsValue::UNDEFINED),
201            EventTarget::Window(s) => (4, s),
202            EventTarget::Webview(s) => (5, s),
203            EventTarget::WebviewWindow(s) => (6, s),
204        },
205    };
206
207    let cmd = cmd.with(|s| JsValue::from(s));
208    let args = ext::eargs(event, payload, kind, label);
209    ext::invoke(&cmd, &args, Options::empty())
210}
211
212/// An argument of event target for the [`to`](Emit::to) function.
213pub enum EventTarget<S> {
214    Any,
215    AnyLabel(S),
216    App,
217    Window(S),
218    Webview(S),
219    WebviewWindow(S),
220}
221
222impl<S> EventTarget<S> {
223    #[inline]
224    pub fn from_string(s: S) -> Self {
225        Self::AnyLabel(s)
226    }
227
228    #[inline]
229    pub fn as_ref(&self) -> EventTarget<&S> {
230        match self {
231            Self::Any => EventTarget::Any,
232            Self::AnyLabel(s) => EventTarget::AnyLabel(s),
233            Self::App => EventTarget::App,
234            Self::Window(s) => EventTarget::Window(s),
235            Self::Webview(s) => EventTarget::Webview(s),
236            Self::WebviewWindow(s) => EventTarget::WebviewWindow(s),
237        }
238    }
239
240    #[inline]
241    pub fn map<F, T>(self, f: F) -> EventTarget<T>
242    where
243        F: FnOnce(S) -> T,
244    {
245        match self {
246            Self::Any => EventTarget::Any,
247            Self::AnyLabel(s) => EventTarget::AnyLabel(f(s)),
248            Self::App => EventTarget::App,
249            Self::Window(s) => EventTarget::Window(f(s)),
250            Self::Webview(s) => EventTarget::Webview(f(s)),
251            Self::WebviewWindow(s) => EventTarget::WebviewWindow(f(s)),
252        }
253    }
254}
255
256impl From<&str> for EventTarget<JsString> {
257    #[inline]
258    fn from(s: &str) -> Self {
259        Self::from_string(JsString::from(s))
260    }
261}