Quellcode durchsuchen

fix(core): window-specific event delivery, closes #3302 (#3344)

Lucas Fernandes Nogueira vor 3 Jahren
Ursprung
Commit
9b34055264

+ 6 - 0
.changes/fix-window-specific-event-system.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch
+---
+
+The `tauri::Window#emit` functiow now correctly sends the event to all windows that has a registered listener.
+**Breaking change:** `Window#emit_and_trigger` and `Window#emit` now requires the payload to be cloneable.

+ 3 - 3
core/tauri-runtime-wry/src/lib.rs

@@ -14,7 +14,7 @@ use tauri_runtime::{
   webview::{FileDropEvent, FileDropHandler, WebviewIpcHandler, WindowBuilder, WindowBuilderBase},
   window::{
     dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
-    DetachedWindow, PendingWindow, WindowEvent,
+    DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
   },
   ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Icon, Result,
   RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
@@ -2700,7 +2700,7 @@ fn create_ipc_handler(
   context: Context,
   label: String,
   menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
-  js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
+  js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
   handler: WebviewIpcHandler<Wry>,
 ) -> Box<dyn Fn(&Window, String) + 'static> {
   Box::new(move |window, request| {
@@ -2724,7 +2724,7 @@ fn create_file_drop_handler(
   context: Context,
   label: String,
   menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
-  js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
+  js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
   handler: FileDropHandler<Wry>,
 ) -> Box<dyn Fn(&Window, WryFileDropEvent) -> bool + 'static> {
   Box::new(move |window, event| {

+ 24 - 9
core/tauri-runtime/src/window.rs

@@ -105,14 +105,20 @@ pub struct PendingWindow<R: Runtime> {
   /// Maps runtime id to a string menu id.
   pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
 
-  /// A HashMap mapping JS event names with listener ids associated.
-  pub js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
+  /// A HashMap mapping JS event names with associated listener ids.
+  pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
 }
 
-fn validate_label(label: &str) {
+pub fn is_label_valid(label: &str) -> bool {
+  label
+    .chars()
+    .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
+}
+
+pub fn assert_label_is_valid(label: &str) {
   assert!(
-    label.chars().all(char::is_alphanumeric),
-    "Window label must be alphanumeric"
+    is_label_valid(label),
+    "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
   );
 }
 
@@ -128,7 +134,7 @@ impl<R: Runtime> PendingWindow<R> {
       get_menu_ids(&mut menu_ids, menu);
     }
     let label = label.into();
-    validate_label(&label);
+    assert_label_is_valid(&label);
     Self {
       window_builder,
       webview_attributes,
@@ -154,7 +160,7 @@ impl<R: Runtime> PendingWindow<R> {
       get_menu_ids(&mut menu_ids, menu);
     }
     let label = label.into();
-    validate_label(&label);
+    assert_label_is_valid(&label);
     Self {
       window_builder,
       webview_attributes,
@@ -192,6 +198,15 @@ impl<R: Runtime> PendingWindow<R> {
   }
 }
 
+/// Key for a JS event listener.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct JsEventListenerKey {
+  /// The associated window label.
+  pub window_label: Option<String>,
+  /// The event name.
+  pub event: String,
+}
+
 /// A webview window that is not yet managed by Tauri.
 #[derive(Debug)]
 pub struct DetachedWindow<R: Runtime> {
@@ -204,8 +219,8 @@ pub struct DetachedWindow<R: Runtime> {
   /// Maps runtime id to a string menu id.
   pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
 
-  /// A HashMap mapping JS event names with listener ids associated.
-  pub js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
+  /// A HashMap mapping JS event names with associated listener ids.
+  pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
 }
 
 impl<R: Runtime> Clone for DetachedWindow<R> {

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
core/tauri/scripts/bundle.js


+ 38 - 5
core/tauri/src/endpoints/event.rs

@@ -7,6 +7,7 @@ use crate::{
   api::ipc::CallbackFn,
   event::is_event_name_valid,
   event::{listen_js, unlisten_js},
+  runtime::window::is_label_valid,
   sealed::ManagerBase,
   Manager, Runtime,
 };
@@ -31,12 +32,35 @@ impl<'de> Deserialize<'de> for EventId {
   }
 }
 
+pub struct WindowLabel(String);
+
+impl<'de> Deserialize<'de> for WindowLabel {
+  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+  where
+    D: Deserializer<'de>,
+  {
+    let event_id = String::deserialize(deserializer)?;
+    if is_label_valid(&event_id) {
+      Ok(WindowLabel(event_id))
+    } else {
+      Err(serde::de::Error::custom(
+        "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`.",
+      ))
+    }
+  }
+}
+
 /// The API descriptor.
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Listen to an event.
-  Listen { event: EventId, handler: CallbackFn },
+  #[serde(rename_all = "camelCase")]
+  Listen {
+    event: EventId,
+    window_label: Option<WindowLabel>,
+    handler: CallbackFn,
+  },
   /// Unlisten to an event.
   #[serde(rename_all = "camelCase")]
   Unlisten { event_id: u64 },
@@ -45,7 +69,7 @@ pub enum Cmd {
   #[serde(rename_all = "camelCase")]
   Emit {
     event: EventId,
-    window_label: Option<String>,
+    window_label: Option<WindowLabel>,
     payload: Option<String>,
   },
 }
@@ -54,16 +78,25 @@ impl Cmd {
   fn listen<R: Runtime>(
     context: InvokeContext<R>,
     event: EventId,
+    window_label: Option<WindowLabel>,
     handler: CallbackFn,
   ) -> crate::Result<u64> {
     let event_id = rand::random();
+
+    let window_label = window_label.map(|l| l.0);
+
     context.window.eval(&listen_js(
       context.window.manager().event_listeners_object_name(),
       format!("'{}'", event.0),
       event_id,
+      window_label.clone(),
       format!("window['_{}']", handler.0),
     ))?;
-    context.window.register_js_listener(event.0, event_id);
+
+    context
+      .window
+      .register_js_listener(window_label, event.0, event_id);
+
     Ok(event_id)
   }
 
@@ -79,14 +112,14 @@ impl Cmd {
   fn emit<R: Runtime>(
     context: InvokeContext<R>,
     event: EventId,
-    window_label: Option<String>,
+    window_label: Option<WindowLabel>,
     payload: Option<String>,
   ) -> crate::Result<()> {
     // dispatch the event to Rust listeners
     context.window.trigger(&event.0, payload.clone());
 
     if let Some(target) = window_label {
-      context.window.emit_to(&target, &event.0, payload)?;
+      context.window.emit_to(&target.0, &event.0, payload)?;
     } else {
       context.window.emit_all(&event.0, payload)?;
     }

+ 17 - 2
core/tauri/src/event.rs

@@ -18,6 +18,13 @@ pub fn is_event_name_valid(event: &str) -> bool {
     .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_')
 }
 
+pub fn assert_event_name_is_valid(event: &str) {
+  assert!(
+    is_event_name_valid(event),
+    "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`."
+  );
+}
+
 /// Represents an event handler.
 #[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub struct EventHandler(Uuid);
@@ -306,23 +313,31 @@ pub fn listen_js(
   listeners_object_name: String,
   event: String,
   event_id: u64,
+  window_label: Option<String>,
   handler: String,
 ) -> String {
   format!(
     "if (window['{listeners}'] === void 0) {{
-      window['{listeners}'] = Object.create(null)
+      Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }});
     }}
     if (window['{listeners}'][{event}] === void 0) {{
-      window['{listeners}'][{event}] = []
+      Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }});
     }}
     window['{listeners}'][{event}].push({{
       id: {event_id},
+      windowLabel: {window_label},
       handler: {handler}
     }});
   ",
     listeners = listeners_object_name,
     event = event,
     event_id = event_id,
+    window_label = if let Some(l) = window_label {
+      crate::runtime::window::assert_label_is_valid(&l);
+      format!("'{}'", l)
+    } else {
+      "null".to_owned()
+    },
     handler = handler
   )
 }

+ 3 - 2
core/tauri/src/lib.rs

@@ -404,14 +404,14 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
 
   /// Emits a event to all windows.
   fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
-    self.manager().emit_filter(event, payload, |_| true)
+    self.manager().emit_filter(event, None, payload, |_| true)
   }
 
   /// Emits an event to a window with the specified label.
   fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
     self
       .manager()
-      .emit_filter(event, payload, |w| label == w.label())
+      .emit_filter(event, None, payload, |w| label == w.label())
   }
 
   /// Listen to a global event.
@@ -552,6 +552,7 @@ pub(crate) mod sealed {
 
       self.manager().emit_filter(
         "tauri://window-created",
+        None,
         Some(WindowCreatedEvent {
           label: window.label().into(),
         }),

+ 31 - 20
core/tauri/src/manager.rs

@@ -30,7 +30,7 @@ use crate::hooks::IsolationJavascript;
 use crate::pattern::{format_real_schema, PatternJavascript};
 use crate::{
   app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
-  event::{is_event_name_valid, Event, EventHandler, Listeners},
+  event::{assert_event_name_is_valid, Event, EventHandler, Listeners},
   hooks::{InvokeHandler, InvokePayload, InvokeResponder, OnPageLoad, PageLoadPayload},
   plugin::PluginStore,
   runtime::{
@@ -850,6 +850,7 @@ impl<R: Runtime> WindowManager<R> {
           self.event_listeners_object_name(),
           "eventName".into(),
           0,
+          None,
           "window['_' + window.__TAURI__.transformCallback(cb) ]".into()
         )
       ),
@@ -865,18 +866,22 @@ impl<R: Runtime> WindowManager<R> {
   fn event_initialization_script(&self) -> String {
     return format!(
       "
-      window['{function}'] = function (eventData) {{
-      const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
-
-      for (let i = listeners.length - 1; i >= 0; i--) {{
-        const listener = listeners[i]
-        eventData.id = listener.id
-        listener.handler(eventData)
-      }}
-    }}
+      Object.defineProperty(window, '{function}', {{
+        value: function (eventData) {{
+          const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
+
+          for (let i = listeners.length - 1; i >= 0; i--) {{
+            const listener = listeners[i]
+            if (listener.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
+              eventData.id = listener.id
+              listener.handler(eventData)
+            }}
+          }}
+        }}
+      }});
     ",
-      function = self.inner.listeners.function_name(),
-      listeners = self.inner.listeners.listeners_object_name()
+      function = self.event_emit_function_name(),
+      listeners = self.event_listeners_object_name()
     );
   }
 }
@@ -1088,17 +1093,23 @@ impl<R: Runtime> WindowManager<R> {
     self.windows_lock().remove(label);
   }
 
-  pub fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> crate::Result<()>
+  pub fn emit_filter<S, F>(
+    &self,
+    event: &str,
+    source_window_label: Option<&str>,
+    payload: S,
+    filter: F,
+  ) -> crate::Result<()>
   where
     S: Serialize + Clone,
     F: Fn(&Window<R>) -> bool,
   {
-    assert!(is_event_name_valid(event));
+    assert_event_name_is_valid(event);
     self
       .windows_lock()
       .values()
       .filter(|&w| filter(w))
-      .try_for_each(|window| window.emit(event, payload.clone()))
+      .try_for_each(|window| window.emit_internal(event, source_window_label, payload.clone()))
   }
 
   pub fn labels(&self) -> HashSet<String> {
@@ -1118,7 +1129,7 @@ impl<R: Runtime> WindowManager<R> {
   }
 
   pub fn trigger(&self, event: &str, window: Option<String>, data: Option<String>) {
-    assert!(is_event_name_valid(event));
+    assert_event_name_is_valid(event);
     self.inner.listeners.trigger(event, window, data)
   }
 
@@ -1128,7 +1139,7 @@ impl<R: Runtime> WindowManager<R> {
     window: Option<String>,
     handler: F,
   ) -> EventHandler {
-    assert!(is_event_name_valid(&event));
+    assert_event_name_is_valid(&event);
     self.inner.listeners.listen(event, window, handler)
   }
 
@@ -1138,7 +1149,7 @@ impl<R: Runtime> WindowManager<R> {
     window: Option<String>,
     handler: F,
   ) -> EventHandler {
-    assert!(is_event_name_valid(&event));
+    assert_event_name_is_valid(&event);
     self.inner.listeners.once(event, window, handler)
   }
 
@@ -1171,7 +1182,7 @@ fn on_window_event<R: Runtime>(
       label: _,
       signal_tx,
     } => {
-      if window.has_js_listener(WINDOW_CLOSE_REQUESTED_EVENT) {
+      if window.has_js_listener(Some(window.label().into()), WINDOW_CLOSE_REQUESTED_EVENT) {
         signal_tx.send(true).unwrap();
       }
       window.emit_and_trigger(WINDOW_CLOSE_REQUESTED_EVENT, ())?;
@@ -1210,7 +1221,7 @@ fn on_window_event<R: Runtime>(
   Ok(())
 }
 
-#[derive(Serialize)]
+#[derive(Clone, Serialize)]
 #[serde(rename_all = "camelCase")]
 struct ScaleFactorChanged {
   scale_factor: f64,

+ 38 - 19
core/tauri/src/window.rs

@@ -19,7 +19,7 @@ use crate::{
     webview::{WebviewAttributes, WindowBuilder},
     window::{
       dpi::{PhysicalPosition, PhysicalSize, Position, Size},
-      DetachedWindow, PendingWindow, WindowEvent,
+      DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
     },
     Dispatch, Icon, Runtime, UserAttentionType,
   },
@@ -261,29 +261,41 @@ impl<R: Runtime> Window<R> {
   }
 
   /// Emits an event to both the JavaScript and the Rust listeners.
-  pub fn emit_and_trigger<S: Serialize>(&self, event: &str, payload: S) -> crate::Result<()> {
+  pub fn emit_and_trigger<S: Serialize + Clone>(
+    &self,
+    event: &str,
+    payload: S,
+  ) -> crate::Result<()> {
     self.trigger(event, Some(serde_json::to_string(&payload)?));
     self.emit(event, payload)
   }
 
-  /// Emits an event to the JavaScript listeners on the current window.
-  ///
-  /// The event is only delivered to listeners that used the `appWindow.listen` method on the @tauri-apps/api `window` module.
-  pub fn emit<S: Serialize>(&self, event: &str, payload: S) -> crate::Result<()> {
+  pub(crate) fn emit_internal<S: Serialize>(
+    &self,
+    event: &str,
+    source_window_label: Option<&str>,
+    payload: S,
+  ) -> crate::Result<()> {
     self.eval(&format!(
-      "window['{}']({{event: {}, payload: {}}})",
+      "window['{}']({{event: {}, windowLabel: {}, payload: {}}})",
       self.manager.event_emit_function_name(),
       serde_json::to_string(event)?,
+      serde_json::to_string(&source_window_label)?,
       serde_json::to_value(payload)?,
     ))?;
     Ok(())
   }
 
-  /// Emits an event to the JavaScript listeners on all windows except this one.
+  /// Emits an event to the JavaScript listeners on the current window.
   ///
-  /// The event is only delivered to listeners that used the `appWindow.listen` function from the `@tauri-apps/api `window` module.
-  pub fn emit_others<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
-    self.manager.emit_filter(event, payload, |w| w != self)
+  /// The event is only delivered to listeners that used the `WebviewWindow#listen` method on the @tauri-apps/api `window` module.
+  pub fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
+    self
+      .manager
+      .emit_filter(event, Some(self.label()), payload, |w| {
+        w.has_js_listener(None, event) || w.has_js_listener(Some(self.label().into()), event)
+      })?;
+    Ok(())
   }
 
   /// Listen to an event on this window.
@@ -346,13 +358,16 @@ impl<R: Runtime> Window<R> {
     })
   }
 
-  pub(crate) fn register_js_listener(&self, event: String, id: u64) {
+  pub(crate) fn register_js_listener(&self, window_label: Option<String>, event: String, id: u64) {
     self
       .window
       .js_event_listeners
       .lock()
       .unwrap()
-      .entry(event)
+      .entry(JsEventListenerKey {
+        window_label,
+        event,
+      })
       .or_insert_with(Default::default)
       .insert(id);
   }
@@ -360,28 +375,32 @@ impl<R: Runtime> Window<R> {
   pub(crate) fn unregister_js_listener(&self, id: u64) {
     let mut empty = None;
     let mut js_listeners = self.window.js_event_listeners.lock().unwrap();
-    for (event, ids) in js_listeners.iter_mut() {
+    for (key, ids) in js_listeners.iter_mut() {
       if ids.contains(&id) {
         ids.remove(&id);
         if ids.is_empty() {
-          empty.replace(event.clone());
+          empty.replace(key.clone());
         }
         break;
       }
     }
 
-    if let Some(event) = empty {
-      js_listeners.remove(&event);
+    if let Some(key) = empty {
+      js_listeners.remove(&key);
     }
   }
 
-  pub(crate) fn has_js_listener(&self, event: &str) -> bool {
+  /// Whether this window registered a listener to an event from the given window and event name.
+  pub(crate) fn has_js_listener(&self, window_label: Option<String>, event: &str) -> bool {
     self
       .window
       .js_event_listeners
       .lock()
       .unwrap()
-      .contains_key(event)
+      .contains_key(&JsEventListenerKey {
+        window_label,
+        event: event.into(),
+      })
   }
 
   // Getters

Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 1
examples/api/dist/assets/index.js


+ 0 - 1
examples/api/isolation-dist/index.js

@@ -1,4 +1,3 @@
 window.__TAURI_ISOLATION_HOOK__= (payload) => {
-  console.log('hook', payload)
   return payload
 }

+ 7 - 5
examples/api/src-tauri/Cargo.lock

@@ -485,9 +485,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "3.0.4"
+version = "3.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d01c9347757e131122b19cd19a05c85805b68c2352a97b623efdc3c295290299"
+checksum = "1957aa4a5fb388f0a0a73ce7556c5b42025b874e5cdc2c670775e346e97adec0"
 dependencies = [
  "atty",
  "bitflags",
@@ -3236,7 +3236,8 @@ dependencies = [
 [[package]]
 name = "tao"
 version = "0.6.0"
-source = "git+https://github.com/tauri-apps/tao?branch=dev#553055f4f44c6c4ba917a6254e1dfd3fbc1d8a3a"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d2bba60298cbc0ff0651fe5a0bdfb73438b91231f700dcc8d539548237cae7"
 dependencies = [
  "bitflags",
  "cairo-rs",
@@ -4132,8 +4133,9 @@ dependencies = [
 
 [[package]]
 name = "wry"
-version = "0.12.2"
-source = "git+https://github.com/tauri-apps/wry?rev=d25273376b88a98f4f92fc378b5aa105f19d602e#d25273376b88a98f4f92fc378b5aa105f19d602e"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4875fbbfc2c63f6c57c4ef84f678b1b57e3b8795443add7fbd02f3e8017e30"
 dependencies = [
  "cocoa",
  "core-graphics",

+ 1 - 1
examples/api/src-tauri/src/main.rs

@@ -20,7 +20,7 @@ use tauri::{
   RunEvent, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl,
 };
 
-#[derive(Serialize)]
+#[derive(Clone, Serialize)]
 struct Reply {
   data: String,
 }

+ 2 - 3
examples/api/src/components/Window.svelte

@@ -1,9 +1,8 @@
 <script>
-  import { appWindow, WebviewWindow, LogicalSize, LogicalPosition, UserAttentionType, PhysicalSize, PhysicalPosition } from "@tauri-apps/api/window";
+  import { appWindow, WebviewWindow, LogicalSize, UserAttentionType, PhysicalSize, PhysicalPosition } from "@tauri-apps/api/window";
   import { open as openDialog } from "@tauri-apps/api/dialog";
   import { open } from "@tauri-apps/api/shell";
 
-  window.UserAttentionType = UserAttentionType;
   let selectedWindow = appWindow.label;
   const windowMap = {
     [selectedWindow]: appWindow
@@ -58,7 +57,7 @@
     openDialog({
       multiple: false,
     }).then(path => {
-      if (path) {
+      if (typeof path === 'string') {
         windowMap[selectedWindow].setIcon(path)
       }
     });

+ 5 - 0
examples/api/vite.config.js

@@ -12,5 +12,10 @@ export default defineConfig({
         assetFileNames: `assets/[name].[ext]`
       }
     }
+  },
+  server: {
+    fs: {
+      allow: ['.', '../../tooling/api/dist']
+    }
   }
 })

+ 10 - 67
tooling/api/src/event.ts

@@ -9,58 +9,13 @@
  * @module
  */
 
-import { invokeTauriCommand } from './helpers/tauri'
-import { emit as emitEvent } from './helpers/event'
-import { transformCallback } from './tauri'
-import { LiteralUnion } from 'type-fest'
-
-interface Event<T> {
-  /** Event name */
-  event: EventName
-  /** Event identifier used to unlisten */
-  id: number
-  /** Event payload */
-  payload: T
-}
-
-type EventName = LiteralUnion<
-  | 'tauri://update'
-  | 'tauri://update-available'
-  | 'tauri://update-install'
-  | 'tauri://update-status'
-  | 'tauri://resize'
-  | 'tauri://move'
-  | 'tauri://close-requested'
-  | 'tauri://focus'
-  | 'tauri://blur'
-  | 'tauri://scale-change'
-  | 'tauri://menu'
-  | 'tauri://file-drop'
-  | 'tauri://file-drop-hover'
-  | 'tauri://file-drop-cancelled',
-  string
->
-
-type EventCallback<T> = (event: Event<T>) => void
-
-type UnlistenFn = () => void
-
-/**
- * Unregister the event listener associated with the given id.
- *
- * @ignore
- * @param eventId Event identifier
- * @returns
- */
-async function _unlisten(eventId: number): Promise<void> {
-  return invokeTauriCommand({
-    __tauriModule: 'Event',
-    message: {
-      cmd: 'unlisten',
-      eventId
-    }
-  })
-}
+import * as eventApi from './helpers/event'
+import type {
+  EventName,
+  EventCallback,
+  UnlistenFn,
+  Event
+} from './helpers/event'
 
 /**
  * Listen to an event from the backend.
@@ -73,16 +28,7 @@ async function listen<T>(
   event: EventName,
   handler: EventCallback<T>
 ): Promise<UnlistenFn> {
-  return invokeTauriCommand<number>({
-    __tauriModule: 'Event',
-    message: {
-      cmd: 'listen',
-      event,
-      handler: transformCallback(handler)
-    }
-  }).then((eventId) => {
-    return async () => _unlisten(eventId)
-  })
+  return eventApi.listen(event, null, handler)
 }
 
 /**
@@ -96,10 +42,7 @@ async function once<T>(
   event: EventName,
   handler: EventCallback<T>
 ): Promise<UnlistenFn> {
-  return listen<T>(event, (eventData) => {
-    handler(eventData)
-    _unlisten(eventData.id).catch(() => {})
-  })
+  return eventApi.once(event, null, handler)
 }
 
 /**
@@ -110,7 +53,7 @@ async function once<T>(
  * @returns
  */
 async function emit(event: string, payload?: unknown): Promise<void> {
-  return emitEvent(event, undefined, payload)
+  return eventApi.emit(event, undefined, payload)
 }
 
 export type { Event, EventName, EventCallback, UnlistenFn }

+ 96 - 1
tooling/api/src/helpers/event.ts

@@ -4,6 +4,58 @@
 
 import { WindowLabel } from '../window'
 import { invokeTauriCommand } from './tauri'
+import { transformCallback } from '../tauri'
+import { LiteralUnion } from 'type-fest'
+
+export interface Event<T> {
+  /** Event name */
+  event: EventName
+  /** The label of the window that emitted this event. */
+  windowLabel: string
+  /** Event identifier used to unlisten */
+  id: number
+  /** Event payload */
+  payload: T
+}
+
+export type EventName = LiteralUnion<
+  | 'tauri://update'
+  | 'tauri://update-available'
+  | 'tauri://update-install'
+  | 'tauri://update-status'
+  | 'tauri://resize'
+  | 'tauri://move'
+  | 'tauri://close-requested'
+  | 'tauri://focus'
+  | 'tauri://blur'
+  | 'tauri://scale-change'
+  | 'tauri://menu'
+  | 'tauri://file-drop'
+  | 'tauri://file-drop-hover'
+  | 'tauri://file-drop-cancelled',
+  string
+>
+
+export type EventCallback<T> = (event: Event<T>) => void
+
+export type UnlistenFn = () => void
+
+/**
+ * Unregister the event listener associated with the given id.
+ *
+ * @ignore
+ * @param eventId Event identifier
+ * @returns
+ */
+async function _unlisten(eventId: number): Promise<void> {
+  return invokeTauriCommand({
+    __tauriModule: 'Event',
+    message: {
+      cmd: 'unlisten',
+      eventId
+    }
+  })
+}
 
 /**
  * Emits an event to the backend.
@@ -29,4 +81,47 @@ async function emit(
   })
 }
 
-export { emit }
+/**
+ * Listen to an event from the backend.
+ *
+ * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
+ * @param handler Event handler callback.
+ * @return A promise resolving to a function to unlisten to the event.
+ */
+async function listen<T>(
+  event: EventName,
+  windowLabel: string | null,
+  handler: EventCallback<T>
+): Promise<UnlistenFn> {
+  return invokeTauriCommand<number>({
+    __tauriModule: 'Event',
+    message: {
+      cmd: 'listen',
+      event,
+      windowLabel,
+      handler: transformCallback(handler)
+    }
+  }).then((eventId) => {
+    return async () => _unlisten(eventId)
+  })
+}
+
+/**
+ * Listen to an one-off event from the backend.
+ *
+ * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
+ * @param handler Event handler callback.
+ * @returns A promise resolving to a function to unlisten to the event.
+ */
+async function once<T>(
+  event: EventName,
+  windowLabel: string | null,
+  handler: EventCallback<T>
+): Promise<UnlistenFn> {
+  return listen<T>(event, windowLabel, (eventData) => {
+    handler(eventData)
+    _unlisten(eventData.id).catch(() => {})
+  })
+}
+
+export { emit, listen, once }

+ 5 - 5
tooling/api/src/window.ts

@@ -83,8 +83,8 @@
  */
 
 import { invokeTauriCommand } from './helpers/tauri'
-import { EventName, EventCallback, UnlistenFn, listen, once } from './event'
-import { emit } from './helpers/event'
+import type { EventName, EventCallback, UnlistenFn } from './event'
+import { emit, listen, once } from './helpers/event'
 
 /** Allows you to retrieve information about a given monitor. */
 interface Monitor {
@@ -252,7 +252,7 @@ class WebviewWindowHandle {
         listeners.splice(listeners.indexOf(handler), 1)
       })
     }
-    return listen(event, handler)
+    return listen(event, this.label, handler)
   }
 
   /**
@@ -270,7 +270,7 @@ class WebviewWindowHandle {
         listeners.splice(listeners.indexOf(handler), 1)
       })
     }
-    return once(event, handler)
+    return once(event, this.label, handler)
   }
 
   /**
@@ -283,7 +283,7 @@ class WebviewWindowHandle {
     if (localTauriEvents.includes(event)) {
       // eslint-disable-next-line
       for (const handler of this.listeners[event] || []) {
-        handler({ event, id: -1, payload })
+        handler({ event, id: -1, windowLabel: this.label, payload })
       }
       return Promise.resolve()
     }

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.