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