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