123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- //! The Tauri plugin extension to expand Tauri functionality.
- use crate::{
- runtime::Runtime, utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, OnPageLoad,
- PageLoadPayload, RunEvent, Window,
- };
- use serde::de::DeserializeOwned;
- use serde_json::Value as JsonValue;
- use tauri_macros::default_runtime;
- use std::{collections::HashMap, fmt};
- /// The result type of Tauri plugin module.
- pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
- /// The plugin interface.
- pub trait Plugin<R: Runtime>: Send {
- /// The plugin name. Used as key on the plugin config object.
- fn name(&self) -> &'static str;
- /// Initializes the plugin.
- #[allow(unused_variables)]
- fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
- Ok(())
- }
- /// The JS script to evaluate on webview initialization.
- /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
- /// so global variables must be assigned to `window` instead of implicity declared.
- ///
- /// It's guaranteed that this script is executed before the page is loaded.
- fn initialization_script(&self) -> Option<String> {
- None
- }
- /// Callback invoked when the webview is created.
- #[allow(unused_variables)]
- fn created(&mut self, window: Window<R>) {}
- /// Callback invoked when the webview performs a navigation to a page.
- #[allow(unused_variables)]
- fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {}
- /// Callback invoked when the event loop receives a new event.
- #[allow(unused_variables)]
- fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
- /// Extend commands to [`crate::Builder::invoke_handler`].
- #[allow(unused_variables)]
- fn extend_api(&mut self, invoke: Invoke<R>) {}
- }
- type SetupHook<R> = dyn Fn(&AppHandle<R>) -> Result<()> + Send + Sync;
- type SetupWithConfigHook<R, T> = dyn Fn(&AppHandle<R>, T) -> Result<()> + Send + Sync;
- type OnWebviewReady<R> = dyn Fn(Window<R>) + Send + Sync;
- type OnEvent<R> = dyn Fn(&AppHandle<R>, &RunEvent) + Send + Sync;
- /// Builds a [`TauriPlugin`].
- pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
- name: &'static str,
- invoke_handler: Box<InvokeHandler<R>>,
- setup: Box<SetupHook<R>>,
- setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
- js_init_script: Option<String>,
- on_page_load: Box<OnPageLoad<R>>,
- on_webview_ready: Box<OnWebviewReady<R>>,
- on_event: Box<OnEvent<R>>,
- }
- impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
- /// Creates a new Plugin builder.
- pub fn new(name: &'static str) -> Self {
- Self {
- name,
- setup: Box::new(|_| Ok(())),
- setup_with_config: None,
- js_init_script: None,
- invoke_handler: Box::new(|_| ()),
- on_page_load: Box::new(|_, _| ()),
- on_webview_ready: Box::new(|_| ()),
- on_event: Box::new(|_, _| ()),
- }
- }
- /// Defines the JS message handler callback.
- #[must_use]
- pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
- where
- F: Fn(Invoke<R>) + Send + Sync + 'static,
- {
- self.invoke_handler = Box::new(invoke_handler);
- self
- }
- /// The JS script to evaluate on webview initialization.
- /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
- /// so global variables must be assigned to `window` instead of implicity declared.
- ///
- /// It's guaranteed that this script is executed before the page is loaded.
- #[must_use]
- pub fn js_init_script(mut self, js_init_script: String) -> Self {
- self.js_init_script = Some(js_init_script);
- self
- }
- /// Define a closure that runs when the app is built.
- ///
- /// This is a convenience function around [setup_with_config], without the need to specify a configuration object.
- ///
- /// The closure gets called before the [setup_with_config] closure.
- ///
- /// [setup_with_config]: struct.Builder.html#method.setup_with_config
- #[must_use]
- pub fn setup<F>(mut self, setup: F) -> Self
- where
- F: Fn(&AppHandle<R>) -> Result<()> + Send + Sync + 'static,
- {
- self.setup = Box::new(setup);
- self
- }
- /// Define a closure that runs when the app is built, accepting a configuration object set on `tauri.conf.json > plugins > yourPluginName`.
- ///
- /// If your plugin is not pulling a configuration object from `tauri.conf.json`, use [setup].
- ///
- /// The closure gets called after the [setup] closure.
- ///
- /// # Example
- ///
- /// ```rust,no_run
- /// #[derive(serde::Deserialize)]
- /// struct Config {
- /// api_url: String,
- /// }
- ///
- /// fn get_plugin<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R, Config> {
- /// tauri::plugin::Builder::<R, Config>::new("api")
- /// .setup_with_config(|_app, config| {
- /// println!("config: {:?}", config.api_url);
- /// Ok(())
- /// })
- /// .build()
- /// }
- ///
- /// tauri::Builder::default().plugin(get_plugin());
- /// ```
- ///
- /// [setup]: struct.Builder.html#method.setup
- #[must_use]
- pub fn setup_with_config<F>(mut self, setup_with_config: F) -> Self
- where
- F: Fn(&AppHandle<R>, C) -> Result<()> + Send + Sync + 'static,
- {
- self.setup_with_config.replace(Box::new(setup_with_config));
- self
- }
- /// Callback invoked when the webview performs a navigation to a page.
- #[must_use]
- pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
- where
- F: Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static,
- {
- self.on_page_load = Box::new(on_page_load);
- self
- }
- /// Callback invoked when the webview is created.
- #[must_use]
- pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
- where
- F: Fn(Window<R>) + Send + Sync + 'static,
- {
- self.on_webview_ready = Box::new(on_webview_ready);
- self
- }
- /// Callback invoked when the event loop receives a new event.
- #[must_use]
- pub fn on_event<F>(mut self, on_event: F) -> Self
- where
- F: Fn(&AppHandle<R>, &RunEvent) + Send + Sync + 'static,
- {
- self.on_event = Box::new(on_event);
- self
- }
- /// Builds the [TauriPlugin].
- pub fn build(self) -> TauriPlugin<R, C> {
- TauriPlugin {
- name: self.name,
- invoke_handler: self.invoke_handler,
- setup: self.setup,
- setup_with_config: self.setup_with_config,
- js_init_script: self.js_init_script,
- on_page_load: self.on_page_load,
- on_webview_ready: self.on_webview_ready,
- on_event: self.on_event,
- }
- }
- }
- /// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
- pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
- name: &'static str,
- invoke_handler: Box<InvokeHandler<R>>,
- setup: Box<SetupHook<R>>,
- setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
- js_init_script: Option<String>,
- on_page_load: Box<OnPageLoad<R>>,
- on_webview_ready: Box<OnWebviewReady<R>>,
- on_event: Box<OnEvent<R>>,
- }
- impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
- fn name(&self) -> &'static str {
- self.name
- }
- fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
- (self.setup)(app)?;
- if let Some(s) = &self.setup_with_config {
- (s)(app, serde_json::from_value(config)?)?;
- }
- Ok(())
- }
- fn initialization_script(&self) -> Option<String> {
- self.js_init_script.clone()
- }
- fn created(&mut self, window: Window<R>) {
- (self.on_webview_ready)(window)
- }
- fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
- (self.on_page_load)(window, payload)
- }
- fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
- (self.on_event)(app, event)
- }
- fn extend_api(&mut self, invoke: Invoke<R>) {
- (self.invoke_handler)(invoke)
- }
- }
- /// Plugin collection type.
- #[default_runtime(crate::Wry, wry)]
- pub(crate) struct PluginStore<R: Runtime> {
- store: HashMap<&'static str, Box<dyn Plugin<R>>>,
- }
- impl<R: Runtime> fmt::Debug for PluginStore<R> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("PluginStore")
- .field("plugins", &self.store.keys())
- .finish()
- }
- }
- impl<R: Runtime> Default for PluginStore<R> {
- fn default() -> Self {
- Self {
- store: HashMap::new(),
- }
- }
- }
- impl<R: Runtime> PluginStore<R> {
- /// Adds a plugin to the store.
- ///
- /// Returns `true` if a plugin with the same name is already in the store.
- pub fn register<P: Plugin<R> + 'static>(&mut self, plugin: P) -> bool {
- self.store.insert(plugin.name(), Box::new(plugin)).is_some()
- }
- /// Initializes all plugins in the store.
- pub(crate) fn initialize(
- &mut self,
- app: &AppHandle<R>,
- config: &PluginConfig,
- ) -> crate::Result<()> {
- self.store.values_mut().try_for_each(|plugin| {
- plugin
- .initialize(
- app,
- config.0.get(plugin.name()).cloned().unwrap_or_default(),
- )
- .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
- })
- }
- /// Generates an initialization script from all plugins in the store.
- pub(crate) fn initialization_script(&self) -> String {
- self
- .store
- .values()
- .filter_map(|p| p.initialization_script())
- .fold(String::new(), |acc, script| {
- format!("{}\n(function () {{ {} }})();", acc, script)
- })
- }
- /// Runs the created hook for all plugins in the store.
- pub(crate) fn created(&mut self, window: Window<R>) {
- self
- .store
- .values_mut()
- .for_each(|plugin| plugin.created(window.clone()))
- }
- /// Runs the on_page_load hook for all plugins in the store.
- pub(crate) fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
- self
- .store
- .values_mut()
- .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
- }
- /// Runs the on_event hook for all plugins in the store.
- pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
- self
- .store
- .values_mut()
- .for_each(|plugin| plugin.on_event(app, event))
- }
- pub(crate) fn extend_api(&mut self, mut invoke: Invoke<R>) {
- let command = invoke.message.command.replace("plugin:", "");
- let mut tokens = command.split('|');
- // safe to unwrap: split always has a least one item
- let target = tokens.next().unwrap();
- if let Some(plugin) = self.store.get_mut(target) {
- invoke.message.command = tokens
- .next()
- .map(|c| c.to_string())
- .unwrap_or_else(String::new);
- plugin.extend_api(invoke);
- } else {
- invoke
- .resolver
- .reject(format!("plugin {} not found", target));
- }
- }
- }
|