Skip to content

Commit

Permalink
refactor: document Emitter/Listner traits panics, refactor check …
Browse files Browse the repository at this point in the history
…into internal struct (#12444)
  • Loading branch information
sftse authored Jan 24, 2025
1 parent f5a59b9 commit 6cbfc48
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 529 deletions.
8 changes: 2 additions & 6 deletions crates/tauri-cli/src/helpers/flock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,11 @@ mod sys {
}

pub(super) fn error_contended(err: &Error) -> bool {
err
.raw_os_error()
.map_or(false, |x| x == ERROR_LOCK_VIOLATION as i32)
err.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32)
}

pub(super) fn error_unsupported(err: &Error) -> bool {
err
.raw_os_error()
.map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32)
err.raw_os_error() == Some(ERROR_INVALID_FUNCTION as i32)
}

pub(super) fn unlock(file: &File) -> Result<()> {
Expand Down
83 changes: 6 additions & 77 deletions crates/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
sealed::{ManagerBase, RuntimeOrDispatch},
utils::{config::Config, Env},
webview::PageLoadPayload,
Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Result,
Context, DeviceEventFilter, Emitter, EventLoopMessage, EventName, Listener, Manager, Monitor,
Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,
};

Expand All @@ -38,7 +38,6 @@ use tauri_runtime::{
};
use tauri_utils::{assets::AssetsIter, PackageInfo};

use serde::Serialize;
use std::{
borrow::Cow,
collections::HashMap,
Expand Down Expand Up @@ -929,7 +928,8 @@ macro_rules! shared_app_impl {
where
F: Fn(Event) + Send + 'static,
{
self.manager.listen(event.into(), EventTarget::App, handler)
let event = EventName::new(event.into()).unwrap();
self.manager.listen(event, EventTarget::App, handler)
}

/// Listen to an event on this app only once.
Expand All @@ -939,7 +939,8 @@ macro_rules! shared_app_impl {
where
F: FnOnce(Event) + Send + 'static,
{
self.manager.once(event.into(), EventTarget::App, handler)
let event = EventName::new(event.into()).unwrap();
self.manager.once(event, EventTarget::App, handler)
}

/// Unlisten to an event on this app.
Expand All @@ -966,79 +967,7 @@ macro_rules! shared_app_impl {
}
}

impl<R: Runtime> Emitter<R> for $app {
/// Emits an event to all [targets](EventTarget).
///
/// # Examples
/// ```
/// use tauri::Emitter;
///
/// #[tauri::command]
/// fn synchronize(app: tauri::AppHandle) {
/// // emits the synchronized event to all webviews
/// app.emit("synchronized", ());
/// }
/// ```
fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
self.manager.emit(event, payload)
}

/// Emits an event to all [targets](EventTarget) matching the given target.
///
/// # Examples
/// ```
/// use tauri::{Emitter, EventTarget};
///
/// #[tauri::command]
/// fn download(app: tauri::AppHandle) {
/// for i in 1..100 {
/// std::thread::sleep(std::time::Duration::from_millis(150));
/// // emit a download progress event to all listeners
/// app.emit_to(EventTarget::any(), "download-progress", i);
/// // emit an event to listeners that used App::listen or AppHandle::listen
/// app.emit_to(EventTarget::app(), "download-progress", i);
/// // emit an event to any webview/window/webviewWindow matching the given label
/// app.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled
/// app.emit_to(EventTarget::labeled("updater"), "download-progress", i);
/// // emit an event to listeners that used WebviewWindow::listen
/// app.emit_to(EventTarget::webview_window("updater"), "download-progress", i);
/// }
/// }
/// ```
fn emit_to<I, S>(&self, target: I, event: &str, payload: S) -> Result<()>
where
I: Into<EventTarget>,
S: Serialize + Clone,
{
self.manager.emit_to(target, event, payload)
}

/// Emits an event to all [targets](EventTarget) based on the given filter.
///
/// # Examples
/// ```
/// use tauri::{Emitter, EventTarget};
///
/// #[tauri::command]
/// fn download(app: tauri::AppHandle) {
/// for i in 1..100 {
/// std::thread::sleep(std::time::Duration::from_millis(150));
/// // emit a download progress event to the updater window
/// app.emit_filter("download-progress", i, |t| match t {
/// EventTarget::WebviewWindow { label } => label == "main",
/// _ => false,
/// });
/// }
/// }
/// ```
fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
where
S: Serialize + Clone,
F: Fn(&EventTarget) -> bool,
{
self.manager.emit_filter(event, payload, filter)
}
}
impl<R: Runtime> Emitter<R> for $app {}
};
}

Expand Down
3 changes: 3 additions & 0 deletions crates/tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ pub enum Error {
/// Bad `__TAURI_INVOKE_KEY__` value received in ipc message.
#[error("bad __TAURI_INVOKE_KEY__ value received in ipc message")]
InvokeKey,
/// Illegal event name.
#[error("only alphanumeric, '-', '/', ':', '_' permitted for event names: {0:?}")]
IllegalEventName(String),
}

impl From<getrandom::Error> for Error {
Expand Down
70 changes: 70 additions & 0 deletions crates/tauri/src/event/event_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use serde::{Deserialize, Deserializer};

/// Checks if an event name is valid.
fn is_event_name_valid(event: &str) -> bool {
event
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_')
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct EventName<S = String>(S);

impl Copy for EventName<&str> {}

impl<S: AsRef<str>> EventName<S> {
pub(crate) fn new(s: S) -> crate::Result<EventName<S>> {
if !is_event_name_valid(s.as_ref()) {
return Err(crate::Error::IllegalEventName(s.as_ref().to_string()));
}
Ok(EventName(s))
}

pub(crate) fn as_str_event(&self) -> EventName<&str> {
EventName(self.0.as_ref())
}

pub(crate) fn as_str(&self) -> &str {
self.0.as_ref()
}
}

impl<S: std::fmt::Display> std::fmt::Display for EventName<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl EventName<&'static str> {
// this convenience method is for using in const contexts to discharge the preconditions
// &'static prevents using this function accidentally with dynamically built string slices
pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> {
EventName(s)
}
}

impl EventName<&str> {
pub fn into_owned(self) -> EventName {
EventName(self.0.to_string())
}
}

impl<'de> Deserialize<'de> for EventName {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let event_id = String::deserialize(deserializer)?;
if is_event_name_valid(&event_id) {
Ok(EventName(event_id))
} else {
Err(serde::de::Error::custom(
"Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.",
))
}
}
}
Loading

0 comments on commit 6cbfc48

Please sign in to comment.