tauri/
lib.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Tauri is a framework for building tiny, blazing fast binaries for all major desktop platforms.
6//! Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface.
7//! The backend of the application is a rust-sourced binary with an API that the front-end can interact with.
8//!
9//! # Cargo features
10//!
11//! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that can be enabled or disabled:
12//!
13//! - **wry** *(enabled by default)*: Enables the [wry](https://github.com/tauri-apps/wry) runtime. Only disable it if you want a custom runtime.
14//! - **common-controls-v6** *(enabled by default)*: Enables [Common Controls v6](https://learn.microsoft.com/en-us/windows/win32/controls/common-control-versions) support on Windows, mainly for the predefined `about` menu item.
15//! - **x11** *(enabled by default)*: Enables X11 support. Disable this if you only target Wayland.
16//! - **unstable**: Enables unstable features. Be careful, it might introduce breaking changes in future minor releases.
17//! - **tracing**: Enables [`tracing`](https://docs.rs/tracing/latest/tracing) for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers.
18//! - **test**: Enables the [`mod@test`] module exposing unit test helpers.
19//! - **objc-exception**: This feature flag is no-op since 2.3.0.
20//! - **linux-libxdo**: Enables linking to libxdo which enables Cut, Copy, Paste and SelectAll menu items to work on Linux.
21//! - **isolation**: Enables the isolation pattern. Enabled by default if the `app > security > pattern > use` config option is set to `isolation` on the `tauri.conf.json` file.
22//! - **custom-protocol**: Feature managed by the Tauri CLI. When enabled, Tauri assumes a production environment instead of a development one.
23//! - **devtools**: Enables the developer tools (Web inspector) and [`window::Window#method.open_devtools`]. Enabled by default on debug builds.
24//!   On macOS it uses private APIs, so you can't enable it if your app will be published to the App Store.
25//! - **native-tls**: Provides TLS support to connect over HTTPS.
26//! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL.
27//! - **rustls-tls**: Provides TLS support to connect over HTTPS using rustls.
28//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website).
29//! - **tray-icon**: Enables application tray icon APIs. Enabled by default if the `trayIcon` config is defined on the `tauri.conf.json` file.
30//! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file.
31//! - **webview-data-url**: Enables usage of data URLs on the webview.
32//! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries.
33//! - **config-json5**: Adds support to JSON5 format for `tauri.conf.json`.
34//! - **config-toml**: Adds support to TOML format for the configuration `Tauri.toml`.
35//! - **image-ico**: Adds support to parse `.ico` image, see [`Image`].
36//! - **image-png**: Adds support to parse `.png` image, see [`Image`].
37//! - **macos-proxy**: Adds support for [`WebviewBuilder::proxy_url`] on macOS. Requires macOS 14+.
38//! - **specta**: Add support for [`specta::specta`](https://docs.rs/specta/%5E2.0.0-rc.9/specta/attr.specta.html) with Tauri arguments such as [`State`](crate::State), [`Window`](crate::Window) and [`AppHandle`](crate::AppHandle)
39//! - **dynamic-acl** *(enabled by default)*: Enables you to add ACLs at runtime, notably it enables the [`Manager::add_capability`] function.
40//!
41//! ## Cargo allowlist features
42//!
43//! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that enables commands for Tauri's API package.
44//! These features are automatically enabled by the Tauri CLI based on the `allowlist` configuration under `tauri.conf.json`.
45//!
46//! ### Protocol allowlist
47//!
48//! - **protocol-asset**: Enables the `asset` custom protocol.
49
50#![doc(
51  html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
52  html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
53)]
54#![warn(missing_docs, rust_2018_idioms)]
55#![cfg_attr(docsrs, feature(doc_cfg))]
56
57/// Setups the binding that initializes an iOS plugin.
58#[cfg(target_os = "ios")]
59#[macro_export]
60macro_rules! ios_plugin_binding {
61  ($fn_name: ident) => {
62    tauri::swift_rs::swift!(fn $fn_name() -> *const ::std::ffi::c_void);
63  }
64}
65#[cfg(target_os = "macos")]
66#[doc(hidden)]
67pub use embed_plist;
68pub use error::{Error, Result};
69use ipc::RuntimeAuthority;
70#[cfg(feature = "dynamic-acl")]
71use ipc::RuntimeCapability;
72pub use resources::{Resource, ResourceId, ResourceTable};
73#[cfg(target_os = "ios")]
74#[doc(hidden)]
75pub use swift_rs;
76pub use tauri_macros::include_image;
77#[cfg(mobile)]
78pub use tauri_macros::mobile_entry_point;
79pub use tauri_macros::{command, generate_handler};
80
81use tauri_utils::assets::AssetsIter;
82pub use url::Url;
83
84pub(crate) mod app;
85pub mod async_runtime;
86mod error;
87mod event;
88pub mod ipc;
89mod manager;
90mod pattern;
91pub mod plugin;
92pub(crate) mod protocol;
93mod resources;
94mod vibrancy;
95pub mod webview;
96pub mod window;
97use tauri_runtime as runtime;
98pub mod image;
99#[cfg(target_os = "ios")]
100mod ios;
101#[cfg(desktop)]
102#[cfg_attr(docsrs, doc(cfg(desktop)))]
103pub mod menu;
104/// Path APIs.
105pub mod path;
106pub mod process;
107/// The allowlist scopes.
108pub mod scope;
109mod state;
110
111#[cfg(all(desktop, feature = "tray-icon"))]
112#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
113pub mod tray;
114pub use tauri_utils as utils;
115
116pub use http;
117
118/// A Tauri [`Runtime`] wrapper around wry.
119#[cfg(feature = "wry")]
120#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
121pub type Wry = tauri_runtime_wry::Wry<EventLoopMessage>;
122/// A Tauri [`RuntimeHandle`] wrapper around wry.
123#[cfg(feature = "wry")]
124#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
125pub type WryHandle = tauri_runtime_wry::WryHandle<EventLoopMessage>;
126
127#[cfg(all(feature = "wry", target_os = "android"))]
128#[cfg_attr(docsrs, doc(cfg(all(feature = "wry", target_os = "android"))))]
129#[doc(hidden)]
130#[macro_export]
131macro_rules! android_binding {
132  ($domain:ident, $app_name:ident, $main:ident, $wry:path) => {
133    use $wry::{
134      android_setup,
135      prelude::{JClass, JNIEnv, JString},
136    };
137
138    ::tauri::wry::android_binding!($domain, $app_name, $wry);
139
140    ::tauri::tao::android_binding!(
141      $domain,
142      $app_name,
143      WryActivity,
144      android_setup,
145      $main,
146      ::tauri::tao
147    );
148
149    // be careful when renaming this, the `Java_app_tauri_plugin_PluginManager_handlePluginResponse` symbol is checked by the CLI
150    ::tauri::tao::platform::android::prelude::android_fn!(
151      app_tauri,
152      plugin,
153      PluginManager,
154      handlePluginResponse,
155      [i32, JString, JString],
156    );
157    ::tauri::tao::platform::android::prelude::android_fn!(
158      app_tauri,
159      plugin,
160      PluginManager,
161      sendChannelData,
162      [i64, JString],
163    );
164
165    // this function is a glue between PluginManager.kt > handlePluginResponse and Rust
166    #[allow(non_snake_case)]
167    pub fn handlePluginResponse(
168      mut env: JNIEnv,
169      _: JClass,
170      id: i32,
171      success: JString,
172      error: JString,
173    ) {
174      ::tauri::handle_android_plugin_response(&mut env, id, success, error);
175    }
176
177    // this function is a glue between PluginManager.kt > sendChannelData and Rust
178    #[allow(non_snake_case)]
179    pub fn sendChannelData(mut env: JNIEnv, _: JClass, id: i64, data: JString) {
180      ::tauri::send_channel_data(&mut env, id, data);
181    }
182  };
183}
184
185#[cfg(all(feature = "wry", target_os = "android"))]
186#[doc(hidden)]
187pub use plugin::mobile::{handle_android_plugin_response, send_channel_data};
188#[cfg(all(feature = "wry", target_os = "android"))]
189#[doc(hidden)]
190pub use tauri_runtime_wry::{tao, wry};
191
192/// A task to run on the main thread.
193pub type SyncTask = Box<dyn FnOnce() + Send>;
194
195use serde::Serialize;
196use std::{
197  borrow::Cow,
198  collections::HashMap,
199  fmt::{self, Debug},
200  sync::MutexGuard,
201};
202use utils::assets::{AssetKey, CspHash, EmbeddedAssets};
203
204#[cfg(feature = "wry")]
205#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
206pub use tauri_runtime_wry::webview_version;
207
208#[cfg(target_os = "macos")]
209#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
210pub use runtime::ActivationPolicy;
211
212pub use self::utils::TitleBarStyle;
213
214use self::event::EventName;
215pub use self::event::{Event, EventId, EventTarget};
216use self::manager::EmitPayload;
217pub use {
218  self::app::{
219    App, AppHandle, AssetResolver, Builder, CloseRequestApi, ExitRequestApi, RunEvent,
220    UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent, RESTART_EXIT_CODE,
221  },
222  self::manager::Asset,
223  self::runtime::{
224    dpi::{
225      LogicalPosition, LogicalRect, LogicalSize, LogicalUnit, PhysicalPosition, PhysicalRect,
226      PhysicalSize, PhysicalUnit, Pixel, PixelUnit, Position, Rect, Size,
227    },
228    window::{CursorIcon, DragDropEvent, WindowSizeConstraints},
229    DeviceEventFilter, UserAttentionType,
230  },
231  self::state::{State, StateManager},
232  self::utils::{
233    config::{Config, WebviewUrl},
234    Env, PackageInfo, Theme,
235  },
236  self::webview::{Webview, WebviewWindow, WebviewWindowBuilder},
237  self::window::{Monitor, Window},
238  scope::*,
239};
240
241#[cfg(feature = "unstable")]
242#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
243pub use {self::webview::WebviewBuilder, self::window::WindowBuilder};
244
245/// The Tauri version.
246pub const VERSION: &str = env!("CARGO_PKG_VERSION");
247
248#[cfg(target_os = "ios")]
249#[doc(hidden)]
250pub fn log_stdout() {
251  use std::{
252    ffi::CString,
253    fs::File,
254    io::{BufRead, BufReader},
255    os::unix::prelude::*,
256    thread,
257  };
258
259  let mut logpipe: [RawFd; 2] = Default::default();
260  unsafe {
261    libc::pipe(logpipe.as_mut_ptr());
262    libc::dup2(logpipe[1], libc::STDOUT_FILENO);
263    libc::dup2(logpipe[1], libc::STDERR_FILENO);
264  }
265  thread::spawn(move || unsafe {
266    let file = File::from_raw_fd(logpipe[0]);
267    let mut reader = BufReader::new(file);
268    let mut buffer = String::new();
269    loop {
270      buffer.clear();
271      if let Ok(len) = reader.read_line(&mut buffer) {
272        if len == 0 {
273          break;
274        } else if let Ok(msg) = CString::new(buffer.as_bytes())
275          .map_err(|_| ())
276          .and_then(|c| c.into_string().map_err(|_| ()))
277        {
278          log::info!("{}", msg);
279        }
280      }
281    }
282  });
283}
284
285/// The user event type.
286#[derive(Debug, Clone)]
287pub enum EventLoopMessage {
288  /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.
289  #[cfg(desktop)]
290  MenuEvent(menu::MenuEvent),
291  /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.
292  #[cfg(all(desktop, feature = "tray-icon"))]
293  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
294  TrayIconEvent(tray::TrayIconEvent),
295}
296
297/// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated.
298pub trait Runtime: runtime::Runtime<EventLoopMessage> {}
299/// The webview runtime handle. A wrapper arond [`runtime::RuntimeHandle`] with the proper user event type associated.
300pub trait RuntimeHandle: runtime::RuntimeHandle<EventLoopMessage> {}
301
302impl<W: runtime::Runtime<EventLoopMessage>> Runtime for W {}
303impl<R: runtime::RuntimeHandle<EventLoopMessage>> RuntimeHandle for R {}
304
305/// Reads the config file at compile time and generates a [`Context`] based on its content.
306///
307/// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of
308/// the crate being built.
309///
310/// # Custom Config Path
311///
312/// You may pass a string literal to this macro to specify a custom path for the Tauri config file.
313/// If the path is relative, it will be search for relative to the Cargo manifest of the compiling
314/// crate.
315///
316/// # Note
317///
318/// This macro should not be called if you are using [`tauri-build`] to generate the context from
319/// inside your build script as it will just cause excess computations that will be discarded. Use
320/// either the [`tauri-build`] method or this macro - not both.
321///
322/// [`tauri-build`]: https://docs.rs/tauri-build
323pub use tauri_macros::generate_context;
324
325/// Include a [`Context`] that was generated by [`tauri-build`] inside your build script.
326///
327/// You should either use [`tauri-build`] and this macro to include the compile time generated code,
328/// or [`generate_context!`]. Do not use both at the same time, as they generate the same code and
329/// will cause excess computations that will be discarded.
330///
331/// [`tauri-build`]: https://docs.rs/tauri-build
332#[macro_export]
333macro_rules! tauri_build_context {
334  () => {
335    include!(concat!(env!("OUT_DIR"), "/tauri-build-context.rs"))
336  };
337}
338
339pub use pattern::Pattern;
340
341/// Whether we are running in development mode or not.
342pub const fn is_dev() -> bool {
343  !cfg!(feature = "custom-protocol")
344}
345
346/// Represents a container of file assets that are retrievable during runtime.
347pub trait Assets<R: Runtime>: Send + Sync + 'static {
348  /// Initialize the asset provider.
349  fn setup(&self, app: &App<R>) {
350    let _ = app;
351  }
352
353  /// Get the content of the passed [`AssetKey`].
354  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;
355
356  /// Iterator for the assets.
357  fn iter(&self) -> Box<tauri_utils::assets::AssetsIter<'_>>;
358
359  /// Gets the hashes for the CSP tag of the HTML on the given path.
360  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
361}
362
363impl<R: Runtime> Assets<R> for EmbeddedAssets {
364  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
365    EmbeddedAssets::get(self, key)
366  }
367
368  fn iter(&self) -> Box<AssetsIter<'_>> {
369    EmbeddedAssets::iter(self)
370  }
371
372  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
373    EmbeddedAssets::csp_hashes(self, html_path)
374  }
375}
376
377/// User supplied data required inside of a Tauri application.
378///
379/// # Stability
380/// This is the output of the [`generate_context`] macro, and is not considered part of the stable API.
381/// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself.
382#[tauri_macros::default_runtime(Wry, wry)]
383pub struct Context<R: Runtime> {
384  pub(crate) config: Config,
385  #[cfg(dev)]
386  pub(crate) config_parent: Option<std::path::PathBuf>,
387  /// Asset provider.
388  pub assets: Box<dyn Assets<R>>,
389  pub(crate) default_window_icon: Option<image::Image<'static>>,
390  pub(crate) app_icon: Option<Vec<u8>>,
391  #[cfg(all(desktop, feature = "tray-icon"))]
392  pub(crate) tray_icon: Option<image::Image<'static>>,
393  pub(crate) package_info: PackageInfo,
394  pub(crate) pattern: Pattern,
395  pub(crate) runtime_authority: RuntimeAuthority,
396  pub(crate) plugin_global_api_scripts: Option<&'static [&'static str]>,
397}
398
399/// Temporary struct that overrides the Debug formatting for the `app_icon` field.
400///
401/// It reduces the output size compared to the default, as that would format the binary
402/// data as a slice of numbers `[65, 66, 67]`. This instead shows the length of the Vec.
403///
404/// For example: `Some([u8; 493])`
405pub(crate) struct DebugAppIcon<'a>(&'a Option<Vec<u8>>);
406
407impl std::fmt::Debug for DebugAppIcon<'_> {
408  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409    match self.0 {
410      Option::None => f.write_str("None"),
411      Option::Some(icon) => f
412        .debug_tuple("Some")
413        .field(&format_args!("[u8; {}]", icon.len()))
414        .finish(),
415    }
416  }
417}
418
419impl<R: Runtime> fmt::Debug for Context<R> {
420  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421    let mut d = f.debug_struct("Context");
422    d.field("config", &self.config)
423      .field("default_window_icon", &self.default_window_icon)
424      .field("app_icon", &DebugAppIcon(&self.app_icon))
425      .field("package_info", &self.package_info)
426      .field("pattern", &self.pattern)
427      .field("plugin_global_api_scripts", &self.plugin_global_api_scripts);
428
429    #[cfg(all(desktop, feature = "tray-icon"))]
430    d.field("tray_icon", &self.tray_icon);
431
432    d.finish()
433  }
434}
435
436impl<R: Runtime> Context<R> {
437  /// The config the application was prepared with.
438  #[inline(always)]
439  pub fn config(&self) -> &Config {
440    &self.config
441  }
442
443  /// A mutable reference to the config the application was prepared with.
444  #[inline(always)]
445  pub fn config_mut(&mut self) -> &mut Config {
446    &mut self.config
447  }
448
449  /// The assets to be served directly by Tauri.
450  #[inline(always)]
451  pub fn assets(&self) -> &dyn Assets<R> {
452    self.assets.as_ref()
453  }
454
455  /// Replace the [`Assets`] implementation and returns the previous value so you can use it as a fallback if desired.
456  #[inline(always)]
457  pub fn set_assets(&mut self, assets: Box<dyn Assets<R>>) -> Box<dyn Assets<R>> {
458    std::mem::replace(&mut self.assets, assets)
459  }
460
461  /// The default window icon Tauri should use when creating windows.
462  #[inline(always)]
463  pub fn default_window_icon(&self) -> Option<&image::Image<'_>> {
464    self.default_window_icon.as_ref()
465  }
466
467  /// Set the default window icon Tauri should use when creating windows.
468  #[inline(always)]
469  pub fn set_default_window_icon(&mut self, icon: Option<image::Image<'static>>) {
470    self.default_window_icon = icon;
471  }
472
473  /// The icon to use on the tray icon.
474  #[cfg(all(desktop, feature = "tray-icon"))]
475  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
476  #[inline(always)]
477  pub fn tray_icon(&self) -> Option<&image::Image<'_>> {
478    self.tray_icon.as_ref()
479  }
480
481  /// Set the icon to use on the tray icon.
482  #[cfg(all(desktop, feature = "tray-icon"))]
483  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
484  #[inline(always)]
485  pub fn set_tray_icon(&mut self, icon: Option<image::Image<'static>>) {
486    self.tray_icon = icon;
487  }
488
489  /// Package information.
490  #[inline(always)]
491  pub fn package_info(&self) -> &PackageInfo {
492    &self.package_info
493  }
494
495  /// A mutable reference to the package information.
496  #[inline(always)]
497  pub fn package_info_mut(&mut self) -> &mut PackageInfo {
498    &mut self.package_info
499  }
500
501  /// The application pattern.
502  #[inline(always)]
503  pub fn pattern(&self) -> &Pattern {
504    &self.pattern
505  }
506
507  /// A mutable reference to the resolved ACL.
508  ///
509  /// # Stability
510  ///
511  /// This API is unstable.
512  #[doc(hidden)]
513  #[inline(always)]
514  pub fn runtime_authority_mut(&mut self) -> &mut RuntimeAuthority {
515    &mut self.runtime_authority
516  }
517
518  /// Create a new [`Context`] from the minimal required items.
519  #[inline(always)]
520  #[allow(clippy::too_many_arguments)]
521  pub fn new(
522    config: Config,
523    assets: Box<dyn Assets<R>>,
524    default_window_icon: Option<image::Image<'static>>,
525    app_icon: Option<Vec<u8>>,
526    package_info: PackageInfo,
527    pattern: Pattern,
528    runtime_authority: RuntimeAuthority,
529    plugin_global_api_scripts: Option<&'static [&'static str]>,
530  ) -> Self {
531    Self {
532      config,
533      #[cfg(dev)]
534      config_parent: None,
535      assets,
536      default_window_icon,
537      app_icon,
538      #[cfg(all(desktop, feature = "tray-icon"))]
539      tray_icon: None,
540      package_info,
541      pattern,
542      runtime_authority,
543      plugin_global_api_scripts,
544    }
545  }
546
547  #[cfg(dev)]
548  #[doc(hidden)]
549  pub fn with_config_parent(&mut self, config_parent: impl AsRef<std::path::Path>) {
550    self
551      .config_parent
552      .replace(config_parent.as_ref().to_owned());
553  }
554}
555
556// TODO: expand these docs
557/// Manages a running application.
558pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
559  /// The application handle associated with this manager.
560  fn app_handle(&self) -> &AppHandle<R> {
561    self.managed_app_handle()
562  }
563
564  /// The [`Config`] the manager was created with.
565  fn config(&self) -> &Config {
566    self.manager().config()
567  }
568
569  /// The [`PackageInfo`] the manager was created with.
570  fn package_info(&self) -> &PackageInfo {
571    self.manager().package_info()
572  }
573
574  /// Fetch a single window from the manager.
575  #[cfg(feature = "unstable")]
576  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
577  fn get_window(&self, label: &str) -> Option<Window<R>> {
578    self.manager().get_window(label)
579  }
580
581  /// Fetch the focused window. Returns `None` if there is not any focused window.
582  #[cfg(feature = "unstable")]
583  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
584  fn get_focused_window(&self) -> Option<Window<R>> {
585    self.manager().get_focused_window()
586  }
587
588  /// Fetch all managed windows.
589  #[cfg(feature = "unstable")]
590  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
591  fn windows(&self) -> HashMap<String, Window<R>> {
592    self.manager().windows()
593  }
594
595  /// Fetch a single webview from the manager.
596  #[cfg(feature = "unstable")]
597  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
598  fn get_webview(&self, label: &str) -> Option<Webview<R>> {
599    self.manager().get_webview(label)
600  }
601
602  /// Fetch all managed webviews.
603  #[cfg(feature = "unstable")]
604  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
605  fn webviews(&self) -> HashMap<String, Webview<R>> {
606    self.manager().webviews()
607  }
608
609  /// Fetch a single webview window from the manager.
610  fn get_webview_window(&self, label: &str) -> Option<WebviewWindow<R>> {
611    self.manager().get_webview(label).and_then(|webview| {
612      let window = webview.window();
613      if window.is_webview_window() {
614        Some(WebviewWindow { window, webview })
615      } else {
616        None
617      }
618    })
619  }
620
621  /// Fetch all managed webview windows.
622  fn webview_windows(&self) -> HashMap<String, WebviewWindow<R>> {
623    self
624      .manager()
625      .webviews()
626      .into_iter()
627      .filter_map(|(label, webview)| {
628        let window = webview.window();
629        if window.is_webview_window() {
630          Some((label, WebviewWindow { window, webview }))
631        } else {
632          None
633        }
634      })
635      .collect::<HashMap<_, _>>()
636  }
637
638  /// Add `state` to the state managed by the application.
639  ///
640  /// If the state for the `T` type has previously been set, the state is unchanged and false is returned. Otherwise true is returned.
641  ///
642  /// Managed state can be retrieved by any command handler via the
643  /// [`State`] guard. In particular, if a value of type `T`
644  /// is managed by Tauri, adding `State<T>` to the list of arguments in a
645  /// command handler instructs Tauri to retrieve the managed value.
646  /// Additionally, [`state`](Self#method.state) can be used to retrieve the value manually.
647  ///
648  /// # Mutability
649  ///
650  /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:
651  ///
652  /// ```rust,no_run
653  /// use std::{collections::HashMap, sync::Mutex};
654  /// use tauri::State;
655  /// // here we use Mutex to achieve interior mutability
656  /// struct Storage {
657  ///   store: Mutex<HashMap<u64, String>>,
658  /// }
659  /// struct Connection;
660  /// struct DbConnection {
661  ///   db: Mutex<Option<Connection>>,
662  /// }
663  ///
664  /// #[tauri::command]
665  /// fn connect(connection: State<DbConnection>) {
666  ///   // initialize the connection, mutating the state with interior mutability
667  ///   *connection.db.lock().unwrap() = Some(Connection {});
668  /// }
669  ///
670  /// #[tauri::command]
671  /// fn storage_insert(key: u64, value: String, storage: State<Storage>) {
672  ///   // mutate the storage behind the Mutex
673  ///   storage.store.lock().unwrap().insert(key, value);
674  /// }
675  ///
676  /// tauri::Builder::default()
677  ///   .manage(Storage { store: Default::default() })
678  ///   .manage(DbConnection { db: Default::default() })
679  ///   .invoke_handler(tauri::generate_handler![connect, storage_insert])
680  ///   // on an actual app, remove the string argument
681  ///   .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
682  ///   .expect("error while running tauri application");
683  /// ```
684  ///
685  /// # Examples
686  ///
687  /// ```rust,no_run
688  /// use tauri::{Manager, State};
689  ///
690  /// struct MyInt(isize);
691  /// struct MyString(String);
692  ///
693  /// #[tauri::command]
694  /// fn int_command(state: State<MyInt>) -> String {
695  ///     format!("The stateful int is: {}", state.0)
696  /// }
697  ///
698  /// #[tauri::command]
699  /// fn string_command<'r>(state: State<'r, MyString>) {
700  ///     println!("state: {}", state.inner().0);
701  /// }
702  ///
703  /// tauri::Builder::default()
704  ///   .setup(|app| {
705  ///     app.manage(MyInt(0));
706  ///     app.manage(MyString("tauri".into()));
707  ///     // `MyInt` is already managed, so `manage()` returns false
708  ///     assert!(!app.manage(MyInt(1)));
709  ///     // read the `MyInt` managed state with the turbofish syntax
710  ///     let int = app.state::<MyInt>();
711  ///     assert_eq!(int.0, 0);
712  ///     // read the `MyString` managed state with the `State` guard
713  ///     let val: State<MyString> = app.state();
714  ///     assert_eq!(val.0, "tauri");
715  ///     Ok(())
716  ///   })
717  ///   .invoke_handler(tauri::generate_handler![int_command, string_command])
718  ///   // on an actual app, remove the string argument
719  ///   .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
720  ///   .expect("error while running tauri application");
721  /// ```
722  fn manage<T>(&self, state: T) -> bool
723  where
724    T: Send + Sync + 'static,
725  {
726    self.manager().state().set(state)
727  }
728
729  /// Removes the state managed by the application for T. Returns the state if it was actually removed.
730  ///
731  /// <div class="warning">
732  ///
733  /// This method is *UNSAFE* and calling it will cause previously obtained references through
734  /// [Manager::state] and [State::inner] to become dangling references.
735  ///
736  /// It is currently deprecated and may be removed in the future.
737  ///
738  /// If you really want to unmanage a state, use [std::sync::Mutex] and [Option::take] to wrap the state instead.
739  ///
740  /// See [tauri-apps/tauri#12721] for more information.
741  ///
742  /// [tauri-apps/tauri#12721]: https://github.com/tauri-apps/tauri/issues/12721
743  ///
744  /// </div>
745  #[deprecated(
746    since = "2.3.0",
747    note = "This method is unsafe, since it can cause dangling references."
748  )]
749  fn unmanage<T>(&self) -> Option<T>
750  where
751    T: Send + Sync + 'static,
752  {
753    // The caller decides to break the safety here, then OK, just let it go.
754    unsafe { self.manager().state().unmanage() }
755  }
756
757  /// Retrieves the managed state for the type `T`.
758  ///
759  /// # Panics
760  ///
761  /// Panics if the state for the type `T` has not been previously [managed](Self::manage).
762  /// Use [try_state](Self::try_state) for a non-panicking version.
763  fn state<T>(&self) -> State<'_, T>
764  where
765    T: Send + Sync + 'static,
766  {
767    self.manager().state.try_get().unwrap_or_else(|| {
768      panic!(
769        "state() called before manage() for {}",
770        std::any::type_name::<T>()
771      )
772    })
773  }
774
775  /// Attempts to retrieve the managed state for the type `T`.
776  ///
777  /// Returns `Some` if the state has previously been [managed](Self::manage). Otherwise returns `None`.
778  fn try_state<T>(&self) -> Option<State<'_, T>>
779  where
780    T: Send + Sync + 'static,
781  {
782    self.manager().state.try_get()
783  }
784
785  /// Get a reference to the resources table of this manager.
786  fn resources_table(&self) -> MutexGuard<'_, ResourceTable>;
787
788  /// Gets the managed [`Env`].
789  fn env(&self) -> Env {
790    self.state::<Env>().inner().clone()
791  }
792
793  /// Gets the scope for the asset protocol.
794  #[cfg(feature = "protocol-asset")]
795  fn asset_protocol_scope(&self) -> scope::fs::Scope {
796    self.state::<Scopes>().inner().asset_protocol.clone()
797  }
798
799  /// The path resolver.
800  fn path(&self) -> &crate::path::PathResolver<R> {
801    self.state::<crate::path::PathResolver<R>>().inner()
802  }
803
804  /// Adds a capability to the app.
805  ///
806  /// Note that by default every capability file in the `src-tauri/capabilities` folder
807  /// are automatically enabled unless specific capabilities are configured in [`tauri.conf.json > app > security > capabilities`],
808  /// so you should use a different director for the runtime-added capabilities or use [tauri_build::Attributes::capabilities_path_pattern].
809  ///
810  /// # Examples
811  /// ```
812  /// use tauri::Manager;
813  ///
814  /// tauri::Builder::default()
815  ///   .setup(|app| {
816  ///     #[cfg(feature = "beta")]
817  ///     app.add_capability(include_str!("../capabilities/beta/cap.json"));
818  ///
819  ///     #[cfg(feature = "stable")]
820  ///     app.add_capability(include_str!("../capabilities/stable/cap.json"));
821  ///     Ok(())
822  ///   });
823  /// ```
824  ///
825  /// The above example assumes the following directory layout:
826  /// ```md
827  /// ├── capabilities
828  /// │   ├── app (default capabilities used by any app flavor)
829  /// |   |   |-- cap.json
830  /// │   ├── beta (capabilities only added to a `beta` flavor)
831  /// |   |   |-- cap.json
832  /// │   ├── stable (capabilities only added to a `stable` flavor)
833  /// |       |-- cap.json
834  /// ```
835  ///
836  /// For this layout to be properly parsed by Tauri, we need to change the build script to
837  ///
838  /// ```skip
839  /// // only pick up capabilities in the capabilities/app folder by default
840  /// let attributes = tauri_build::Attributes::new().capabilities_path_pattern("./capabilities/app/*.json");
841  /// tauri_build::try_build(attributes).unwrap();
842  /// ```
843  ///
844  /// [`tauri.conf.json > app > security > capabilities`]: https://tauri.app/reference/config/#capabilities
845  /// [tauri_build::Attributes::capabilities_path_pattern]: https://docs.rs/tauri-build/2/tauri_build/struct.Attributes.html#method.capabilities_path_pattern
846  #[cfg(feature = "dynamic-acl")]
847  fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> {
848    self
849      .manager()
850      .runtime_authority
851      .lock()
852      .unwrap()
853      .add_capability(capability)
854  }
855}
856
857/// Listen to events.
858pub trait Listener<R: Runtime>: sealed::ManagerBase<R> {
859  /// Listen to an emitted event on this manager.
860  ///
861  /// # Examples
862  /// ```
863  /// use tauri::{Manager, Listener, Emitter};
864  ///
865  /// #[tauri::command]
866  /// fn synchronize(window: tauri::Window) {
867  ///   // emits the synchronized event to all windows
868  ///   window.emit("synchronized", ());
869  /// }
870  ///
871  /// tauri::Builder::default()
872  ///   .setup(|app| {
873  ///     app.listen("synchronized", |event| {
874  ///       println!("app is in sync");
875  ///     });
876  ///     Ok(())
877  ///   })
878  ///   .invoke_handler(tauri::generate_handler![synchronize]);
879  /// ```
880  /// # Panics
881  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`
882  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
883  where
884    F: Fn(Event) + Send + 'static;
885
886  /// Listen to an event on this manager only once.
887  ///
888  /// See [`Self::listen`] for more information.
889  /// # Panics
890  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`
891  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
892  where
893    F: FnOnce(Event) + Send + 'static;
894
895  /// Remove an event listener.
896  ///
897  /// # Examples
898  /// ```
899  /// use tauri::{Manager, Listener};
900  ///
901  /// tauri::Builder::default()
902  ///   .setup(|app| {
903  ///     let handle = app.handle().clone();
904  ///     let handler = app.listen_any("ready", move |event| {
905  ///       println!("app is ready");
906  ///
907  ///       // we no longer need to listen to the event
908  ///       // we also could have used `app.once_global` instead
909  ///       handle.unlisten(event.id());
910  ///     });
911  ///
912  ///     // stop listening to the event when you do not need it anymore
913  ///     app.unlisten(handler);
914  ///
915  ///
916  ///     Ok(())
917  ///   });
918  /// ```
919  fn unlisten(&self, id: EventId);
920
921  /// Listen to an emitted event to any [target](EventTarget).
922  ///
923  /// # Examples
924  /// ```
925  /// use tauri::{Manager, Emitter, Listener};
926  ///
927  /// #[tauri::command]
928  /// fn synchronize(window: tauri::Window) {
929  ///   // emits the synchronized event to all windows
930  ///   window.emit("synchronized", ());
931  /// }
932  ///
933  /// tauri::Builder::default()
934  ///   .setup(|app| {
935  ///     app.listen_any("synchronized", |event| {
936  ///       println!("app is in sync");
937  ///     });
938  ///     Ok(())
939  ///   })
940  ///   .invoke_handler(tauri::generate_handler![synchronize]);
941  /// ```
942  /// # Panics
943  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`
944  fn listen_any<F>(&self, event: impl Into<String>, handler: F) -> EventId
945  where
946    F: Fn(Event) + Send + 'static,
947  {
948    let event = EventName::new(event.into()).unwrap();
949    self.manager().listen(event, EventTarget::Any, handler)
950  }
951
952  /// Listens once to an emitted event to any [target](EventTarget) .
953  ///
954  /// See [`Self::listen_any`] for more information.
955  /// # Panics
956  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`
957  fn once_any<F>(&self, event: impl Into<String>, handler: F) -> EventId
958  where
959    F: FnOnce(Event) + Send + 'static,
960  {
961    let event = EventName::new(event.into()).unwrap();
962    self.manager().once(event, EventTarget::Any, handler)
963  }
964}
965
966/// Emit events.
967pub trait Emitter<R: Runtime>: sealed::ManagerBase<R> {
968  /// Emits an event to all [targets](EventTarget).
969  ///
970  /// # Examples
971  /// ```
972  /// use tauri::Emitter;
973  ///
974  /// #[tauri::command]
975  /// fn synchronize(app: tauri::AppHandle) {
976  ///   // emits the synchronized event to all webviews
977  ///   app.emit("synchronized", ());
978  /// }
979  /// ```
980  fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
981    let event = EventName::new(event)?;
982    let payload = EmitPayload::Serialize(&payload);
983    self.manager().emit(event, payload)
984  }
985
986  /// Similar to [`Emitter::emit`] but the payload is json serialized.
987  fn emit_str(&self, event: &str, payload: String) -> Result<()> {
988    let event = EventName::new(event)?;
989    let payload = EmitPayload::<()>::Str(payload);
990    self.manager().emit(event, payload)
991  }
992
993  /// Emits an event to all [targets](EventTarget) matching the given target.
994  ///
995  /// # Examples
996  /// ```
997  /// use tauri::{Emitter, EventTarget};
998  ///
999  /// #[tauri::command]
1000  /// fn download(app: tauri::AppHandle) {
1001  ///   for i in 1..100 {
1002  ///     std::thread::sleep(std::time::Duration::from_millis(150));
1003  ///     // emit a download progress event to all listeners
1004  ///     app.emit_to(EventTarget::any(), "download-progress", i);
1005  ///     // emit an event to listeners that used App::listen or AppHandle::listen
1006  ///     app.emit_to(EventTarget::app(), "download-progress", i);
1007  ///     // emit an event to any webview/window/webviewWindow matching the given label
1008  ///     app.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled
1009  ///     app.emit_to(EventTarget::labeled("updater"), "download-progress", i);
1010  ///     // emit an event to listeners that used WebviewWindow::listen
1011  ///     app.emit_to(EventTarget::webview_window("updater"), "download-progress", i);
1012  ///   }
1013  /// }
1014  /// ```
1015  fn emit_to<I, S>(&self, target: I, event: &str, payload: S) -> Result<()>
1016  where
1017    I: Into<EventTarget>,
1018    S: Serialize + Clone,
1019  {
1020    let event = EventName::new(event)?;
1021    let payload = EmitPayload::Serialize(&payload);
1022    self.manager().emit_to(target.into(), event, payload)
1023  }
1024
1025  /// Similar to [`Emitter::emit_to`] but the payload is json serialized.
1026  fn emit_str_to<I>(&self, target: I, event: &str, payload: String) -> Result<()>
1027  where
1028    I: Into<EventTarget>,
1029  {
1030    let event = EventName::new(event)?;
1031    let payload = EmitPayload::<()>::Str(payload);
1032    self.manager().emit_to(target.into(), event, payload)
1033  }
1034
1035  /// Emits an event to all [targets](EventTarget) based on the given filter.
1036  ///
1037  /// # Examples
1038  /// ```
1039  /// use tauri::{Emitter, EventTarget};
1040  ///
1041  /// #[tauri::command]
1042  /// fn download(app: tauri::AppHandle) {
1043  ///   for i in 1..100 {
1044  ///     std::thread::sleep(std::time::Duration::from_millis(150));
1045  ///     // emit a download progress event to the updater window
1046  ///     app.emit_filter("download-progress", i, |t| match t {
1047  ///       EventTarget::WebviewWindow { label } => label == "main",
1048  ///       _ => false,
1049  ///     });
1050  ///   }
1051  /// }
1052  /// ```
1053  fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
1054  where
1055    S: Serialize + Clone,
1056    F: Fn(&EventTarget) -> bool,
1057  {
1058    let event = EventName::new(event)?;
1059    let payload = EmitPayload::Serialize(&payload);
1060    self.manager().emit_filter(event, payload, filter)
1061  }
1062
1063  /// Similar to [`Emitter::emit_filter`] but the payload is json serialized.
1064  fn emit_str_filter<F>(&self, event: &str, payload: String, filter: F) -> Result<()>
1065  where
1066    F: Fn(&EventTarget) -> bool,
1067  {
1068    let event = EventName::new(event)?;
1069    let payload = EmitPayload::<()>::Str(payload);
1070    self.manager().emit_filter(event, payload, filter)
1071  }
1072}
1073
1074/// Prevent implementation details from leaking out of the [`Manager`] trait.
1075pub(crate) mod sealed {
1076  use super::Runtime;
1077  use crate::{app::AppHandle, manager::AppManager};
1078  use std::sync::Arc;
1079
1080  /// A running [`Runtime`] or a dispatcher to it.
1081  pub enum RuntimeOrDispatch<'r, R: Runtime> {
1082    /// Reference to the running [`Runtime`].
1083    Runtime(&'r R),
1084
1085    /// Handle to the running [`Runtime`].
1086    RuntimeHandle(R::Handle),
1087
1088    /// A dispatcher to the running [`Runtime`].
1089    Dispatch(R::WindowDispatcher),
1090  }
1091
1092  /// Managed handle to the application runtime.
1093  pub trait ManagerBase<R: Runtime> {
1094    fn manager(&self) -> &AppManager<R>;
1095    fn manager_owned(&self) -> Arc<AppManager<R>>;
1096    fn runtime(&self) -> RuntimeOrDispatch<'_, R>;
1097    fn managed_app_handle(&self) -> &AppHandle<R>;
1098  }
1099}
1100
1101struct UnsafeSend<T>(T);
1102unsafe impl<T> Send for UnsafeSend<T> {}
1103
1104impl<T> UnsafeSend<T> {
1105  fn take(self) -> T {
1106    self.0
1107  }
1108}
1109
1110#[allow(unused)]
1111macro_rules! run_main_thread {
1112  ($handle:ident, $ex:expr) => {{
1113    use std::sync::mpsc::channel;
1114    let (tx, rx) = channel();
1115    let task = move || {
1116      let f = $ex;
1117      let _ = tx.send(f());
1118    };
1119    $handle
1120      .run_on_main_thread(task)
1121      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
1122  }};
1123}
1124
1125#[allow(unused)]
1126pub(crate) use run_main_thread;
1127
1128#[cfg(any(test, feature = "test"))]
1129#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
1130pub mod test;
1131
1132#[cfg(feature = "specta")]
1133const _: () = {
1134  use specta::{datatype::DataType, function::FunctionArg, TypeMap};
1135
1136  impl<T: Send + Sync + 'static> FunctionArg for crate::State<'_, T> {
1137    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
1138      None
1139    }
1140  }
1141
1142  impl<R: crate::Runtime> FunctionArg for crate::AppHandle<R> {
1143    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
1144      None
1145    }
1146  }
1147
1148  impl<R: crate::Runtime> FunctionArg for crate::Window<R> {
1149    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
1150      None
1151    }
1152  }
1153
1154  impl<R: crate::Runtime> FunctionArg for crate::Webview<R> {
1155    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
1156      None
1157    }
1158  }
1159
1160  impl<R: crate::Runtime> FunctionArg for crate::WebviewWindow<R> {
1161    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
1162      None
1163    }
1164  }
1165};
1166
1167#[cfg(test)]
1168mod tests {
1169  use cargo_toml::Manifest;
1170  use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};
1171
1172  static MANIFEST: OnceLock<Manifest> = OnceLock::new();
1173  const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
1174
1175  fn get_manifest() -> &'static Manifest {
1176    MANIFEST.get_or_init(|| {
1177      let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
1178      Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest")
1179    })
1180  }
1181
1182  #[test]
1183  fn features_are_documented() {
1184    let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
1185    let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
1186
1187    for f in get_manifest().features.keys() {
1188      if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) {
1189        panic!("Feature {f} is not documented");
1190      }
1191    }
1192  }
1193
1194  #[test]
1195  fn aliased_features_exist() {
1196    let checked_features = CHECKED_FEATURES.split(',');
1197    let manifest = get_manifest();
1198    for checked_feature in checked_features {
1199      if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
1200        panic!(
1201          "Feature {checked_feature} was checked in the alias build step but it does not exist in crates/tauri/Cargo.toml"
1202        );
1203      }
1204    }
1205  }
1206}
1207
1208#[cfg(test)]
1209mod test_utils {
1210  use proptest::prelude::*;
1211
1212  pub fn assert_send<T: Send>() {}
1213  pub fn assert_sync<T: Sync>() {}
1214
1215  #[allow(dead_code)]
1216  pub fn assert_not_allowlist_error<T>(res: anyhow::Result<T>) {
1217    if let Err(e) = res {
1218      assert!(!e.to_string().contains("not on the allowlist"));
1219    }
1220  }
1221
1222  proptest! {
1223    #![proptest_config(ProptestConfig::with_cases(10000))]
1224    #[test]
1225    // check to see if spawn executes a function.
1226    fn check_spawn_task(task in "[a-z]+") {
1227      // create dummy task function
1228      let dummy_task = async move {
1229        let _ = format!("{task}-run-dummy-task");
1230      };
1231      // call spawn
1232      crate::async_runtime::spawn(dummy_task);
1233    }
1234  }
1235}
1236
1237/// Simple dependency-free string encoder using [Z85].
1238mod z85 {
1239  const TABLE: &[u8; 85] =
1240    b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
1241
1242  /// Encode bytes with [Z85].
1243  ///
1244  /// # Panics
1245  ///
1246  /// Will panic if the input bytes are not a multiple of 4.
1247  pub fn encode(bytes: &[u8]) -> String {
1248    assert_eq!(bytes.len() % 4, 0);
1249
1250    let mut buf = String::with_capacity(bytes.len() * 5 / 4);
1251    for chunk in bytes.chunks_exact(4) {
1252      let mut chars = [0u8; 5];
1253      let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize;
1254      for byte in chars.iter_mut().rev() {
1255        *byte = TABLE[chunk % 85];
1256        chunk /= 85;
1257      }
1258
1259      buf.push_str(std::str::from_utf8(&chars).unwrap());
1260    }
1261
1262    buf
1263  }
1264
1265  #[cfg(test)]
1266  mod tests {
1267    #[test]
1268    fn encode() {
1269      assert_eq!(
1270        super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]),
1271        "HelloWorld"
1272      );
1273    }
1274  }
1275}
1276
1277/// Generate a random 128-bit [Z85] encoded [`String`].
1278///
1279/// [Z85]: https://rfc.zeromq.org/spec/32/
1280pub(crate) fn generate_invoke_key() -> Result<String> {
1281  let mut bytes = [0u8; 16];
1282  getrandom::fill(&mut bytes)?;
1283  Ok(z85::encode(&bytes))
1284}