Skip to main content

tauri/
plugin.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//! The Tauri plugin extension to expand Tauri functionality.
6
7use crate::{
8  AppHandle, Error, RunEvent, Runtime, UriSchemeContext, Webview, Window,
9  app::UriSchemeResponder,
10  ipc::{Invoke, InvokeHandler, ScopeObject, ScopeValue},
11  manager::webview::UriSchemeProtocol,
12  utils::config::PluginConfig,
13  webview::PageLoadPayload,
14};
15use serde::{
16  Serialize, Serializer,
17  de::{Deserialize, DeserializeOwned, Deserializer, Error as DeError},
18};
19use serde_json::Value as JsonValue;
20use tauri_macros::default_runtime;
21use tauri_runtime::webview::InitializationScript;
22use thiserror::Error;
23use url::Url;
24
25use std::{
26  borrow::Cow,
27  collections::HashMap,
28  fmt::{self, Debug},
29  sync::Arc,
30};
31
32/// Mobile APIs.
33#[cfg(mobile)]
34pub mod mobile;
35
36/// The plugin interface.
37pub trait Plugin<R: Runtime>: Send {
38  /// The plugin name. Used as key on the plugin config object.
39  fn name(&self) -> &'static str;
40
41  /// Initializes the plugin.
42  #[allow(unused_variables)]
43  fn initialize(
44    &mut self,
45    app: &AppHandle<R>,
46    config: JsonValue,
47  ) -> Result<(), Box<dyn std::error::Error>> {
48    Ok(())
49  }
50
51  /// Add the provided JavaScript to a list of scripts that should be run after the global object has been created,
52  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
53  ///
54  /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
55  /// so global variables must be assigned to `window` instead of implicitly declared.
56  ///
57  /// This is executed only on the main frame.
58  /// If you only want to run it in all frames, use [`Plugin::initialization_script_2`] to set that to false.
59  ///
60  /// ## Platform-specific
61  ///
62  /// - **Windows:** scripts are always added to subframes.
63  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
64  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
65  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
66  ///
67  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
68  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
69  fn initialization_script(&self) -> Option<String> {
70    None
71  }
72
73  // TODO: Change `initialization_script` to this in v3
74  /// Same as [`Plugin::initialization_script`] but returns an [`InitializationScript`] instead
75  /// We plan to replace [`Plugin::initialization_script`] with this signature in v3
76  fn initialization_script_2(&self) -> Option<InitializationScript> {
77    self
78      .initialization_script()
79      .map(|script| InitializationScript {
80        script,
81        for_main_frame_only: true,
82      })
83  }
84
85  /// Callback invoked when the window is created.
86  #[allow(unused_variables)]
87  fn window_created(&mut self, window: Window<R>) {}
88
89  /// Callback invoked when the webview is created.
90  #[allow(unused_variables)]
91  fn webview_created(&mut self, webview: Webview<R>) {}
92
93  /// Callback invoked when webview tries to navigate to the given Url. Returning false cancels navigation.
94  #[allow(unused_variables)]
95  fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
96    true
97  }
98
99  /// Callback invoked when the webview performs a navigation to a page.
100  #[allow(unused_variables)]
101  fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {}
102
103  /// Callback invoked when the event loop receives a new event.
104  #[allow(unused_variables)]
105  fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
106
107  // TODO: Change this to `run_invoke_handler` in v3
108  /// Extend commands to [`crate::Builder::invoke_handler`].
109  #[allow(unused_variables)]
110  fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
111    false
112  }
113}
114
115type SetupHook<R, C> =
116  dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>> + Send;
117type OnWindowReady<R> = dyn FnMut(Window<R>) + Send;
118type OnWebviewReady<R> = dyn FnMut(Webview<R>) + Send;
119type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
120type OnNavigation<R> = dyn Fn(&Webview<R>, &Url) -> bool + Send;
121type OnPageLoad<R> = dyn FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send;
122type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
123
124/// A handle to a plugin.
125#[derive(Debug)]
126#[allow(dead_code)]
127pub struct PluginHandle<R: Runtime> {
128  name: &'static str,
129  handle: AppHandle<R>,
130}
131
132impl<R: Runtime> Clone for PluginHandle<R> {
133  fn clone(&self) -> Self {
134    Self {
135      name: self.name,
136      handle: self.handle.clone(),
137    }
138  }
139}
140
141impl<R: Runtime> PluginHandle<R> {
142  /// Returns the application handle.
143  pub fn app(&self) -> &AppHandle<R> {
144    &self.handle
145  }
146}
147
148/// Api exposed to the plugin setup hook.
149#[derive(Clone)]
150#[allow(dead_code)]
151pub struct PluginApi<R: Runtime, C: DeserializeOwned> {
152  handle: AppHandle<R>,
153  name: &'static str,
154  raw_config: Arc<JsonValue>,
155  config: C,
156}
157
158impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
159  /// Returns the plugin configuration.
160  pub fn config(&self) -> &C {
161    &self.config
162  }
163
164  /// Returns the application handle.
165  pub fn app(&self) -> &AppHandle<R> {
166    &self.handle
167  }
168
169  /// Gets the global scope defined on the permissions that are part of the app ACL.
170  pub fn scope<T: ScopeObject>(&self) -> crate::Result<ScopeValue<T>> {
171    self
172      .handle
173      .manager
174      .runtime_authority
175      .lock()
176      .unwrap()
177      .scope_manager
178      .get_global_scope_typed(&self.handle, self.name)
179  }
180}
181
182/// Errors that can happen during [`Builder`].
183#[derive(Debug, Clone, Hash, PartialEq, Error)]
184#[non_exhaustive]
185pub enum BuilderError {
186  /// Plugin attempted to use a reserved name.
187  #[error("plugin uses reserved name: {0}")]
188  ReservedName(String),
189}
190
191const RESERVED_PLUGIN_NAMES: &[&str] = &["core", "tauri"];
192
193/// Builds a [`TauriPlugin`].
194///
195/// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly.
196///
197/// # Conventions
198///
199/// When using the Builder Pattern it is encouraged to export a function called `init` that constructs and returns the plugin.
200/// While plugin authors can provide every possible way to construct a plugin,
201/// sticking to the `init` function convention helps users to quickly identify the correct function to call.
202///
203/// ```rust
204/// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
205///
206/// pub fn init<R: Runtime>() -> TauriPlugin<R> {
207///   Builder::new("example")
208///     .build()
209/// }
210/// ```
211///
212/// When plugins expose more complex configuration options, it can be helpful to provide a Builder instead:
213///
214/// ```rust
215/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
216///
217/// pub struct Builder {
218///   option_a: String,
219///   option_b: String,
220///   option_c: bool
221/// }
222///
223/// impl Default for Builder {
224///   fn default() -> Self {
225///     Self {
226///       option_a: "foo".to_string(),
227///       option_b: "bar".to_string(),
228///       option_c: false
229///     }
230///   }
231/// }
232///
233/// impl Builder {
234///   pub fn new() -> Self {
235///     Default::default()
236///   }
237///
238///   pub fn option_a(mut self, option_a: String) -> Self {
239///     self.option_a = option_a;
240///     self
241///   }
242///
243///   pub fn option_b(mut self, option_b: String) -> Self {
244///     self.option_b = option_b;
245///     self
246///   }
247///
248///   pub fn option_c(mut self, option_c: bool) -> Self {
249///     self.option_c = option_c;
250///     self
251///   }
252///
253///   pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
254///     PluginBuilder::new("example")
255///       .setup(move |app_handle, api| {
256///         // use the options here to do stuff
257///         println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c);
258///
259///         Ok(())
260///       })
261///       .build()
262///   }
263/// }
264/// ```
265pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
266  name: &'static str,
267  invoke_handler: Box<InvokeHandler<R>>,
268  setup: Option<Box<SetupHook<R, C>>>,
269  js_init_script: Option<InitializationScript>,
270  on_navigation: Box<OnNavigation<R>>,
271  on_page_load: Box<OnPageLoad<R>>,
272  on_window_ready: Box<OnWindowReady<R>>,
273  on_webview_ready: Box<OnWebviewReady<R>>,
274  on_event: Box<OnEvent<R>>,
275  on_drop: Option<Box<OnDrop<R>>>,
276  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
277}
278
279impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
280  /// Creates a new Plugin builder.
281  pub fn new(name: &'static str) -> Self {
282    Self {
283      name,
284      setup: None,
285      js_init_script: None,
286      invoke_handler: Box::new(|_| false),
287      on_navigation: Box::new(|_, _| true),
288      on_page_load: Box::new(|_, _| ()),
289      on_window_ready: Box::new(|_| ()),
290      on_webview_ready: Box::new(|_| ()),
291      on_event: Box::new(|_, _| ()),
292      on_drop: None,
293      uri_scheme_protocols: Default::default(),
294    }
295  }
296
297  /// Defines the JS message handler callback.
298  /// It is recommended you use the [tauri::generate_handler] to generate the input to this method, as the input type is not considered stable yet.
299  ///
300  /// # Examples
301  ///
302  /// ```rust
303  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
304  ///
305  /// #[tauri::command]
306  /// async fn foobar<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
307  ///   println!("foobar");
308  ///
309  ///   Ok(())
310  /// }
311  ///
312  /// fn init<R: Runtime>() -> TauriPlugin<R> {
313  ///   Builder::new("example")
314  ///     .invoke_handler(tauri::generate_handler![foobar])
315  ///     .build()
316  /// }
317  ///
318  /// ```
319  /// [tauri::generate_handler]: ../macro.generate_handler.html
320  #[must_use]
321  pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
322  where
323    F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
324  {
325    self.invoke_handler = Box::new(invoke_handler);
326    self
327  }
328
329  /// Sets the provided JavaScript to be run after the global object has been created,
330  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
331  ///
332  /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
333  /// so global variables must be assigned to `window` instead of implicitly declared.
334  ///
335  /// Note that calling this function multiple times overrides previous values.
336  ///
337  /// This is executed only on the main frame.
338  /// If you only want to run it in all frames, use [`Self::js_init_script_on_all_frames`] instead.
339  ///
340  /// ## Platform-specific
341  ///
342  /// - **Windows:** scripts are always added to subframes.
343  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
344  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
345  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
346  ///
347  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
348  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
349  ///
350  /// # Examples
351  ///
352  /// ```rust
353  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
354  ///
355  /// const INIT_SCRIPT: &str = r#"
356  ///   if (window.location.origin === 'https://tauri.app') {
357  ///     console.log("hello world from js init script");
358  ///
359  ///     window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
360  ///   }
361  /// "#;
362  ///
363  /// fn init<R: Runtime>() -> TauriPlugin<R> {
364  ///   Builder::new("example")
365  ///     .js_init_script(INIT_SCRIPT)
366  ///     .build()
367  /// }
368  /// ```
369  #[must_use]
370  // TODO: Rename to `initialization_script` in v3
371  pub fn js_init_script(mut self, js_init_script: impl Into<String>) -> Self {
372    self.js_init_script = Some(InitializationScript {
373      script: js_init_script.into(),
374      for_main_frame_only: true,
375    });
376    self
377  }
378
379  /// Sets the provided JavaScript to be run after the global object has been created,
380  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
381  ///
382  /// Since it runs on all top-level document and child frame page navigations,
383  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
384  ///
385  /// Note that calling this function multiple times overrides previous values.
386  ///
387  /// This is executed on all frames, main frame and also sub frames.
388  /// If you only want to run it in the main frame, use [`Self::js_init_script`] instead.
389  ///
390  /// ## Platform-specific
391  ///
392  /// - **Windows:** scripts are always added to subframes.
393  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
394  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
395  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
396  ///
397  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
398  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
399  #[must_use]
400  pub fn js_init_script_on_all_frames(mut self, js_init_script: impl Into<String>) -> Self {
401    self.js_init_script = Some(InitializationScript {
402      script: js_init_script.into(),
403      for_main_frame_only: false,
404    });
405    self
406  }
407
408  /// Define a closure that runs when the plugin is registered.
409  ///
410  /// # Examples
411  ///
412  /// ```rust
413  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager};
414  /// use std::path::PathBuf;
415  ///
416  /// #[derive(Debug, Default)]
417  /// struct PluginState {
418  ///    dir: Option<PathBuf>
419  /// }
420  ///
421  /// fn init<R: Runtime>() -> TauriPlugin<R> {
422  /// Builder::new("example")
423  ///   .setup(|app, api| {
424  ///     app.manage(PluginState::default());
425  ///
426  ///     Ok(())
427  ///   })
428  ///   .build()
429  /// }
430  /// ```
431  #[must_use]
432  pub fn setup<F>(mut self, setup: F) -> Self
433  where
434    F: FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>>
435      + Send
436      + 'static,
437  {
438    self.setup.replace(Box::new(setup));
439    self
440  }
441
442  /// Callback invoked when the webview tries to navigate to a URL. Returning false cancels the navigation.
443  ///
444  /// #Example
445  ///
446  /// ```
447  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
448  ///
449  /// fn init<R: Runtime>() -> TauriPlugin<R> {
450  ///   Builder::new("example")
451  ///     .on_navigation(|webview, url| {
452  ///       // allow the production URL or localhost on dev
453  ///       url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
454  ///     })
455  ///     .build()
456  /// }
457  /// ```
458  #[must_use]
459  pub fn on_navigation<F>(mut self, on_navigation: F) -> Self
460  where
461    F: Fn(&Webview<R>, &Url) -> bool + Send + 'static,
462  {
463    self.on_navigation = Box::new(on_navigation);
464    self
465  }
466
467  /// Callback invoked when the webview performs a navigation to a page.
468  ///
469  /// # Examples
470  ///
471  /// ```rust
472  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
473  ///
474  /// fn init<R: Runtime>() -> TauriPlugin<R> {
475  ///   Builder::new("example")
476  ///     .on_page_load(|webview, payload| {
477  ///       println!("{:?} URL {} in webview {}", payload.event(), payload.url(), webview.label());
478  ///     })
479  ///     .build()
480  /// }
481  /// ```
482  #[must_use]
483  pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
484  where
485    F: FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send + 'static,
486  {
487    self.on_page_load = Box::new(on_page_load);
488    self
489  }
490
491  /// Callback invoked when the window is created.
492  ///
493  /// # Examples
494  ///
495  /// ```rust
496  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
497  ///
498  /// fn init<R: Runtime>() -> TauriPlugin<R> {
499  ///   Builder::new("example")
500  ///     .on_window_ready(|window| {
501  ///       println!("created window {}", window.label());
502  ///     })
503  ///     .build()
504  /// }
505  /// ```
506  #[must_use]
507  pub fn on_window_ready<F>(mut self, on_window_ready: F) -> Self
508  where
509    F: FnMut(Window<R>) + Send + 'static,
510  {
511    self.on_window_ready = Box::new(on_window_ready);
512    self
513  }
514
515  /// Callback invoked when the webview is created.
516  ///
517  /// # Examples
518  ///
519  /// ```rust
520  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
521  ///
522  /// fn init<R: Runtime>() -> TauriPlugin<R> {
523  ///   Builder::new("example")
524  ///     .on_webview_ready(|webview| {
525  ///       println!("created webview {}", webview.label());
526  ///     })
527  ///     .build()
528  /// }
529  /// ```
530  #[must_use]
531  pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
532  where
533    F: FnMut(Webview<R>) + Send + 'static,
534  {
535    self.on_webview_ready = Box::new(on_webview_ready);
536    self
537  }
538
539  /// Callback invoked when the event loop receives a new event.
540  ///
541  /// # Examples
542  ///
543  /// ```rust
544  /// use tauri::{plugin::{Builder, TauriPlugin}, RunEvent, Runtime};
545  ///
546  /// fn init<R: Runtime>() -> TauriPlugin<R> {
547  ///   Builder::new("example")
548  ///     .on_event(|app_handle, event| {
549  ///       match event {
550  ///         RunEvent::ExitRequested { api, .. } => {
551  ///           // Prevents the app from exiting.
552  ///           // This will cause the core thread to continue running in the background even without any open windows.
553  ///           api.prevent_exit();
554  ///         }
555  ///         // Ignore all other cases.
556  ///         _ => {}
557  ///       }
558  ///     })
559  ///     .build()
560  /// }
561  /// ```
562  #[must_use]
563  pub fn on_event<F>(mut self, on_event: F) -> Self
564  where
565    F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,
566  {
567    self.on_event = Box::new(on_event);
568    self
569  }
570
571  /// Callback invoked when the plugin is dropped.
572  ///
573  /// # Examples
574  ///
575  /// ```rust
576  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
577  ///
578  /// fn init<R: Runtime>() -> TauriPlugin<R> {
579  ///   Builder::new("example")
580  ///     .on_drop(|app| {
581  ///       println!("plugin has been dropped and is no longer running");
582  ///       // you can run cleanup logic here
583  ///     })
584  ///     .build()
585  /// }
586  /// ```
587  #[must_use]
588  pub fn on_drop<F>(mut self, on_drop: F) -> Self
589  where
590    F: FnOnce(AppHandle<R>) + Send + 'static,
591  {
592    self.on_drop.replace(Box::new(on_drop));
593    self
594  }
595
596  /// Registers a URI scheme protocol available to all webviews.
597  ///
598  /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
599  /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
600  /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
601  ///
602  /// # Known limitations
603  ///
604  /// URI scheme protocols are registered when the webview is created. Due to this limitation, if the plugin is registered after a webview has been created, this protocol won't be available.
605  ///
606  /// # Arguments
607  ///
608  /// * `uri_scheme` The URI scheme to register, such as `example`.
609  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
610  ///
611  /// # Examples
612  ///
613  /// ```rust
614  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
615  ///
616  /// fn init<R: Runtime>() -> TauriPlugin<R> {
617  ///   Builder::new("myplugin")
618  ///     .register_uri_scheme_protocol("myscheme", |_ctx, req| {
619  ///       http::Response::builder().body(Vec::new()).unwrap()
620  ///     })
621  ///     .build()
622  /// }
623  /// ```
624  ///
625  /// # Warning
626  ///
627  /// Pages loaded from a custom protocol will have a different Origin on different platforms.
628  /// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`
629  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
630  /// different Origin headers across platforms:
631  ///
632  /// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).
633  /// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).
634  ///   To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].
635  #[must_use]
636  pub fn register_uri_scheme_protocol<
637    N: Into<String>,
638    T: Into<Cow<'static, [u8]>>,
639    H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>) -> http::Response<T>
640      + Send
641      + Sync
642      + 'static,
643  >(
644    mut self,
645    uri_scheme: N,
646    protocol_handler: H,
647  ) -> Self {
648    self.uri_scheme_protocols.insert(
649      uri_scheme.into(),
650      Arc::new(UriSchemeProtocol {
651        handler: Box::new(move |ctx, request, responder| {
652          responder.respond(protocol_handler(ctx, request))
653        }),
654      }),
655    );
656    self
657  }
658
659  /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you
660  /// to process the request in a separate thread and respond asynchronously.
661  ///
662  /// # Arguments
663  ///
664  /// * `uri_scheme` The URI scheme to register, such as `example`.
665  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
666  ///
667  /// # Examples
668  ///
669  /// ```rust
670  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
671  ///
672  /// fn init<R: Runtime>() -> TauriPlugin<R> {
673  ///   Builder::new("myplugin")
674  ///     .register_asynchronous_uri_scheme_protocol("app-files", |_ctx, request, responder| {
675  ///       // skip leading `/`
676  ///       let path = request.uri().path()[1..].to_string();
677  ///       std::thread::spawn(move || {
678  ///         if let Ok(data) = std::fs::read(path) {
679  ///           responder.respond(
680  ///             http::Response::builder()
681  ///               .body(data)
682  ///               .unwrap()
683  ///           );
684  ///         } else {
685  ///           responder.respond(
686  ///             http::Response::builder()
687  ///               .status(http::StatusCode::BAD_REQUEST)
688  ///               .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
689  ///               .body("failed to read file".as_bytes().to_vec())
690  ///               .unwrap()
691  ///           );
692  ///         }
693  ///       });
694  ///     })
695  ///     .build()
696  /// }
697  /// ```
698  ///
699  /// # Warning
700  ///
701  /// Pages loaded from a custom protocol will have a different Origin on different platforms.
702  /// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`
703  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
704  /// different Origin headers across platforms:
705  ///
706  /// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).
707  /// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).
708  ///   To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].
709  #[must_use]
710  pub fn register_asynchronous_uri_scheme_protocol<
711    N: Into<String>,
712    H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,
713  >(
714    mut self,
715    uri_scheme: N,
716    protocol_handler: H,
717  ) -> Self {
718    self.uri_scheme_protocols.insert(
719      uri_scheme.into(),
720      Arc::new(UriSchemeProtocol {
721        handler: Box::new(protocol_handler),
722      }),
723    );
724    self
725  }
726
727  /// Builds the [`TauriPlugin`].
728  pub fn try_build(self) -> Result<TauriPlugin<R, C>, BuilderError> {
729    if let Some(&reserved) = RESERVED_PLUGIN_NAMES.iter().find(|&r| r == &self.name) {
730      return Err(BuilderError::ReservedName(reserved.into()));
731    }
732
733    Ok(TauriPlugin {
734      name: self.name,
735      app: None,
736      invoke_handler: self.invoke_handler,
737      setup: self.setup,
738      js_init_script: self.js_init_script,
739      on_navigation: self.on_navigation,
740      on_page_load: self.on_page_load,
741      on_window_ready: self.on_window_ready,
742      on_webview_ready: self.on_webview_ready,
743      on_event: self.on_event,
744      on_drop: self.on_drop,
745      uri_scheme_protocols: self.uri_scheme_protocols,
746    })
747  }
748
749  /// Builds the [`TauriPlugin`].
750  ///
751  /// # Panics
752  ///
753  /// If the builder returns an error during [`Self::try_build`], then this method will panic.
754  pub fn build(self) -> TauriPlugin<R, C> {
755    self.try_build().expect("valid plugin")
756  }
757}
758
759/// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
760pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
761  name: &'static str,
762  app: Option<AppHandle<R>>,
763  invoke_handler: Box<InvokeHandler<R>>,
764  setup: Option<Box<SetupHook<R, C>>>,
765  js_init_script: Option<InitializationScript>,
766  on_navigation: Box<OnNavigation<R>>,
767  on_page_load: Box<OnPageLoad<R>>,
768  on_window_ready: Box<OnWindowReady<R>>,
769  on_webview_ready: Box<OnWebviewReady<R>>,
770  on_event: Box<OnEvent<R>>,
771  on_drop: Option<Box<OnDrop<R>>>,
772  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
773}
774
775impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
776  fn drop(&mut self) {
777    if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {
778      on_drop(app);
779    }
780  }
781}
782
783impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
784  fn name(&self) -> &'static str {
785    self.name
786  }
787
788  fn initialize(
789    &mut self,
790    app: &AppHandle<R>,
791    config: JsonValue,
792  ) -> Result<(), Box<dyn std::error::Error>> {
793    self.app.replace(app.clone());
794    if let Some(s) = self.setup.take() {
795      (s)(
796        app,
797        PluginApi {
798          name: self.name,
799          handle: app.clone(),
800          raw_config: Arc::new(config.clone()),
801          config: serde_json::from_value(config).map_err(|err| {
802            format!(
803              "Error deserializing 'plugins.{}' within your Tauri configuration: {err}",
804              self.name
805            )
806          })?,
807        },
808      )?;
809    }
810
811    for (uri_scheme, protocol) in &self.uri_scheme_protocols {
812      app
813        .manager
814        .webview
815        .register_uri_scheme_protocol(uri_scheme, protocol.clone())
816    }
817    Ok(())
818  }
819
820  fn initialization_script(&self) -> Option<String> {
821    self
822      .js_init_script
823      .clone()
824      .map(|initialization_script| initialization_script.script)
825  }
826
827  fn initialization_script_2(&self) -> Option<InitializationScript> {
828    self.js_init_script.clone()
829  }
830
831  fn window_created(&mut self, window: Window<R>) {
832    (self.on_window_ready)(window)
833  }
834
835  fn webview_created(&mut self, webview: Webview<R>) {
836    (self.on_webview_ready)(webview)
837  }
838
839  fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
840    (self.on_navigation)(webview, url)
841  }
842
843  fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {
844    (self.on_page_load)(webview, payload)
845  }
846
847  fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
848    (self.on_event)(app, event)
849  }
850
851  fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
852    (self.invoke_handler)(invoke)
853  }
854}
855
856/// Plugin collection type.
857#[default_runtime(crate::Wry, wry)]
858pub(crate) struct PluginStore<R: Runtime> {
859  store: Vec<Box<dyn Plugin<R>>>,
860}
861
862impl<R: Runtime> fmt::Debug for PluginStore<R> {
863  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
864    let plugins: Vec<&str> = self.store.iter().map(|plugins| plugins.name()).collect();
865    f.debug_struct("PluginStore")
866      .field("plugins", &plugins)
867      .finish()
868  }
869}
870
871impl<R: Runtime> Default for PluginStore<R> {
872  fn default() -> Self {
873    Self { store: Vec::new() }
874  }
875}
876
877impl<R: Runtime> PluginStore<R> {
878  /// Adds a plugin to the store.
879  ///
880  /// Returns `true` if a plugin with the same name is already in the store.
881  pub fn register(&mut self, plugin: Box<dyn Plugin<R>>) -> bool {
882    let len = self.store.len();
883    self.store.retain(|p| p.name() != plugin.name());
884    let result = len != self.store.len();
885    self.store.push(plugin);
886    result
887  }
888
889  /// Removes the plugin with the given name from the store.
890  pub fn unregister(&mut self, plugin: &str) -> bool {
891    let len = self.store.len();
892    self.store.retain(|p| p.name() != plugin);
893    len != self.store.len()
894  }
895
896  /// Initializes the given plugin.
897  pub(crate) fn initialize(
898    &self,
899    plugin: &mut Box<dyn Plugin<R>>,
900    app: &AppHandle<R>,
901    config: &PluginConfig,
902  ) -> crate::Result<()> {
903    initialize(plugin, app, config)
904  }
905
906  /// Initializes all plugins in the store.
907  pub(crate) fn initialize_all(
908    &mut self,
909    app: &AppHandle<R>,
910    config: &PluginConfig,
911  ) -> crate::Result<()> {
912    self
913      .store
914      .iter_mut()
915      .try_for_each(|plugin| initialize(plugin, app, config))
916  }
917
918  /// Generates an initialization script from all plugins in the store.
919  pub(crate) fn initialization_script(&self) -> Vec<InitializationScript> {
920    self
921      .store
922      .iter()
923      .filter_map(|p| p.initialization_script_2())
924      .map(
925        |InitializationScript {
926           script,
927           for_main_frame_only,
928         }| InitializationScript {
929          script: format!("(function () {{ {script} }})();"),
930          for_main_frame_only,
931        },
932      )
933      .collect()
934  }
935
936  /// Runs the created hook for all plugins in the store.
937  pub(crate) fn window_created(&mut self, window: Window<R>) {
938    self.store.iter_mut().for_each(|plugin| {
939      #[cfg(feature = "tracing")]
940      let _span = tracing::trace_span!("plugin::hooks::created", name = plugin.name()).entered();
941      plugin.window_created(window.clone())
942    })
943  }
944
945  /// Runs the webview created hook for all plugins in the store.
946  pub(crate) fn webview_created(&mut self, webview: Webview<R>) {
947    self
948      .store
949      .iter_mut()
950      .for_each(|plugin| plugin.webview_created(webview.clone()))
951  }
952
953  pub(crate) fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
954    for plugin in self.store.iter_mut() {
955      #[cfg(feature = "tracing")]
956      let _span =
957        tracing::trace_span!("plugin::hooks::on_navigation", name = plugin.name()).entered();
958      if !plugin.on_navigation(webview, url) {
959        return false;
960      }
961    }
962    true
963  }
964
965  /// Runs the on_page_load hook for all plugins in the store.
966  pub(crate) fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {
967    self.store.iter_mut().for_each(|plugin| {
968      #[cfg(feature = "tracing")]
969      let _span =
970        tracing::trace_span!("plugin::hooks::on_page_load", name = plugin.name()).entered();
971      plugin.on_page_load(webview, payload)
972    })
973  }
974
975  /// Runs the on_event hook for all plugins in the store.
976  pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
977    self
978      .store
979      .iter_mut()
980      .for_each(|plugin| plugin.on_event(app, event))
981  }
982
983  /// Runs the plugin [`Plugin::extend_api`] hook if it exists. Returns whether the invoke message was handled or not.
984  ///
985  /// The message is not handled when the plugin exists **and** the command does not.
986  pub(crate) fn run_invoke_handler(&mut self, plugin: &str, invoke: Invoke<R>) -> bool {
987    for p in self.store.iter_mut() {
988      if p.name() == plugin {
989        #[cfg(feature = "tracing")]
990        let _span = tracing::trace_span!("plugin::hooks::ipc", name = plugin).entered();
991        return p.extend_api(invoke);
992      }
993    }
994    invoke.resolver.reject(format!("plugin {plugin} not found"));
995    true
996  }
997}
998
999#[cfg_attr(feature = "tracing", tracing::instrument(name = "plugin::hooks::initialize", skip(plugin, app), fields(name = plugin.name())))]
1000fn initialize<R: Runtime>(
1001  plugin: &mut Box<dyn Plugin<R>>,
1002  app: &AppHandle<R>,
1003  config: &PluginConfig,
1004) -> crate::Result<()> {
1005  plugin
1006    .initialize(
1007      app,
1008      config.0.get(plugin.name()).cloned().unwrap_or_default(),
1009    )
1010    .map_err(|e| Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
1011}
1012
1013/// Permission state.
1014#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1015#[cfg_attr(feature = "specta", derive(specta::Type))]
1016pub enum PermissionState {
1017  /// Permission access has been granted.
1018  Granted,
1019  /// Permission access has been denied.
1020  Denied,
1021  /// Permission must be requested.
1022  #[default]
1023  Prompt,
1024  /// Permission must be requested, but you must explain to the user why your app needs that permission. **Android only**.
1025  PromptWithRationale,
1026}
1027
1028impl std::fmt::Display for PermissionState {
1029  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1030    match self {
1031      Self::Granted => write!(f, "granted"),
1032      Self::Denied => write!(f, "denied"),
1033      Self::Prompt => write!(f, "prompt"),
1034      Self::PromptWithRationale => write!(f, "prompt-with-rationale"),
1035    }
1036  }
1037}
1038
1039impl Serialize for PermissionState {
1040  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1041  where
1042    S: Serializer,
1043  {
1044    serializer.serialize_str(self.to_string().as_ref())
1045  }
1046}
1047
1048impl<'de> Deserialize<'de> for PermissionState {
1049  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1050  where
1051    D: Deserializer<'de>,
1052  {
1053    let s = <String as Deserialize>::deserialize(deserializer)?;
1054    match s.to_lowercase().as_str() {
1055      "granted" => Ok(Self::Granted),
1056      "denied" => Ok(Self::Denied),
1057      "prompt" => Ok(Self::Prompt),
1058      "prompt-with-rationale" => Ok(Self::PromptWithRationale),
1059      _ => Err(DeError::custom(format!("unknown permission state '{s}'"))),
1060    }
1061  }
1062}