plugin.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! The Tauri plugin extension to expand Tauri functionality.
  5. use crate::{
  6. runtime::Runtime, utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, OnPageLoad,
  7. PageLoadPayload, RunEvent, Window,
  8. };
  9. use serde::de::DeserializeOwned;
  10. use serde_json::Value as JsonValue;
  11. use tauri_macros::default_runtime;
  12. use std::{collections::HashMap, fmt};
  13. /// The result type of Tauri plugin module.
  14. pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
  15. /// The plugin interface.
  16. pub trait Plugin<R: Runtime>: Send {
  17. /// The plugin name. Used as key on the plugin config object.
  18. fn name(&self) -> &'static str;
  19. /// Initializes the plugin.
  20. #[allow(unused_variables)]
  21. fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
  22. Ok(())
  23. }
  24. /// The JS script to evaluate on webview initialization.
  25. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  26. /// so global variables must be assigned to `window` instead of implicity declared.
  27. ///
  28. /// It's guaranteed that this script is executed before the page is loaded.
  29. fn initialization_script(&self) -> Option<String> {
  30. None
  31. }
  32. /// Callback invoked when the webview is created.
  33. #[allow(unused_variables)]
  34. fn created(&mut self, window: Window<R>) {}
  35. /// Callback invoked when the webview performs a navigation to a page.
  36. #[allow(unused_variables)]
  37. fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {}
  38. /// Callback invoked when the event loop receives a new event.
  39. #[allow(unused_variables)]
  40. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
  41. /// Extend commands to [`crate::Builder::invoke_handler`].
  42. #[allow(unused_variables)]
  43. fn extend_api(&mut self, invoke: Invoke<R>) {}
  44. }
  45. type SetupHook<R> = dyn Fn(&AppHandle<R>) -> Result<()> + Send + Sync;
  46. type SetupWithConfigHook<R, T> = dyn Fn(&AppHandle<R>, T) -> Result<()> + Send + Sync;
  47. type OnWebviewReady<R> = dyn Fn(Window<R>) + Send + Sync;
  48. type OnEvent<R> = dyn Fn(&AppHandle<R>, &RunEvent) + Send + Sync;
  49. /// Builds a [`TauriPlugin`].
  50. pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
  51. name: &'static str,
  52. invoke_handler: Box<InvokeHandler<R>>,
  53. setup: Box<SetupHook<R>>,
  54. setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
  55. js_init_script: Option<String>,
  56. on_page_load: Box<OnPageLoad<R>>,
  57. on_webview_ready: Box<OnWebviewReady<R>>,
  58. on_event: Box<OnEvent<R>>,
  59. }
  60. impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
  61. /// Creates a new Plugin builder.
  62. pub fn new(name: &'static str) -> Self {
  63. Self {
  64. name,
  65. setup: Box::new(|_| Ok(())),
  66. setup_with_config: None,
  67. js_init_script: None,
  68. invoke_handler: Box::new(|_| ()),
  69. on_page_load: Box::new(|_, _| ()),
  70. on_webview_ready: Box::new(|_| ()),
  71. on_event: Box::new(|_, _| ()),
  72. }
  73. }
  74. /// Defines the JS message handler callback.
  75. #[must_use]
  76. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  77. where
  78. F: Fn(Invoke<R>) + Send + Sync + 'static,
  79. {
  80. self.invoke_handler = Box::new(invoke_handler);
  81. self
  82. }
  83. /// The JS script to evaluate on webview initialization.
  84. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  85. /// so global variables must be assigned to `window` instead of implicity declared.
  86. ///
  87. /// It's guaranteed that this script is executed before the page is loaded.
  88. #[must_use]
  89. pub fn js_init_script(mut self, js_init_script: String) -> Self {
  90. self.js_init_script = Some(js_init_script);
  91. self
  92. }
  93. /// Define a closure that runs when the app is built.
  94. ///
  95. /// This is a convenience function around [setup_with_config], without the need to specify a configuration object.
  96. ///
  97. /// The closure gets called before the [setup_with_config] closure.
  98. ///
  99. /// [setup_with_config]: struct.Builder.html#method.setup_with_config
  100. #[must_use]
  101. pub fn setup<F>(mut self, setup: F) -> Self
  102. where
  103. F: Fn(&AppHandle<R>) -> Result<()> + Send + Sync + 'static,
  104. {
  105. self.setup = Box::new(setup);
  106. self
  107. }
  108. /// Define a closure that runs when the app is built, accepting a configuration object set on `tauri.conf.json > plugins > yourPluginName`.
  109. ///
  110. /// If your plugin is not pulling a configuration object from `tauri.conf.json`, use [setup].
  111. ///
  112. /// The closure gets called after the [setup] closure.
  113. ///
  114. /// # Example
  115. ///
  116. /// ```rust,no_run
  117. /// #[derive(serde::Deserialize)]
  118. /// struct Config {
  119. /// api_url: String,
  120. /// }
  121. ///
  122. /// fn get_plugin<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R, Config> {
  123. /// tauri::plugin::Builder::<R, Config>::new("api")
  124. /// .setup_with_config(|_app, config| {
  125. /// println!("config: {:?}", config.api_url);
  126. /// Ok(())
  127. /// })
  128. /// .build()
  129. /// }
  130. ///
  131. /// tauri::Builder::default().plugin(get_plugin());
  132. /// ```
  133. ///
  134. /// [setup]: struct.Builder.html#method.setup
  135. #[must_use]
  136. pub fn setup_with_config<F>(mut self, setup_with_config: F) -> Self
  137. where
  138. F: Fn(&AppHandle<R>, C) -> Result<()> + Send + Sync + 'static,
  139. {
  140. self.setup_with_config.replace(Box::new(setup_with_config));
  141. self
  142. }
  143. /// Callback invoked when the webview performs a navigation to a page.
  144. #[must_use]
  145. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  146. where
  147. F: Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static,
  148. {
  149. self.on_page_load = Box::new(on_page_load);
  150. self
  151. }
  152. /// Callback invoked when the webview is created.
  153. #[must_use]
  154. pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
  155. where
  156. F: Fn(Window<R>) + Send + Sync + 'static,
  157. {
  158. self.on_webview_ready = Box::new(on_webview_ready);
  159. self
  160. }
  161. /// Callback invoked when the event loop receives a new event.
  162. #[must_use]
  163. pub fn on_event<F>(mut self, on_event: F) -> Self
  164. where
  165. F: Fn(&AppHandle<R>, &RunEvent) + Send + Sync + 'static,
  166. {
  167. self.on_event = Box::new(on_event);
  168. self
  169. }
  170. /// Builds the [TauriPlugin].
  171. pub fn build(self) -> TauriPlugin<R, C> {
  172. TauriPlugin {
  173. name: self.name,
  174. invoke_handler: self.invoke_handler,
  175. setup: self.setup,
  176. setup_with_config: self.setup_with_config,
  177. js_init_script: self.js_init_script,
  178. on_page_load: self.on_page_load,
  179. on_webview_ready: self.on_webview_ready,
  180. on_event: self.on_event,
  181. }
  182. }
  183. }
  184. /// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
  185. pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
  186. name: &'static str,
  187. invoke_handler: Box<InvokeHandler<R>>,
  188. setup: Box<SetupHook<R>>,
  189. setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
  190. js_init_script: Option<String>,
  191. on_page_load: Box<OnPageLoad<R>>,
  192. on_webview_ready: Box<OnWebviewReady<R>>,
  193. on_event: Box<OnEvent<R>>,
  194. }
  195. impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
  196. fn name(&self) -> &'static str {
  197. self.name
  198. }
  199. fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
  200. (self.setup)(app)?;
  201. if let Some(s) = &self.setup_with_config {
  202. (s)(app, serde_json::from_value(config)?)?;
  203. }
  204. Ok(())
  205. }
  206. fn initialization_script(&self) -> Option<String> {
  207. self.js_init_script.clone()
  208. }
  209. fn created(&mut self, window: Window<R>) {
  210. (self.on_webview_ready)(window)
  211. }
  212. fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
  213. (self.on_page_load)(window, payload)
  214. }
  215. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  216. (self.on_event)(app, event)
  217. }
  218. fn extend_api(&mut self, invoke: Invoke<R>) {
  219. (self.invoke_handler)(invoke)
  220. }
  221. }
  222. /// Plugin collection type.
  223. #[default_runtime(crate::Wry, wry)]
  224. pub(crate) struct PluginStore<R: Runtime> {
  225. store: HashMap<&'static str, Box<dyn Plugin<R>>>,
  226. }
  227. impl<R: Runtime> fmt::Debug for PluginStore<R> {
  228. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  229. f.debug_struct("PluginStore")
  230. .field("plugins", &self.store.keys())
  231. .finish()
  232. }
  233. }
  234. impl<R: Runtime> Default for PluginStore<R> {
  235. fn default() -> Self {
  236. Self {
  237. store: HashMap::new(),
  238. }
  239. }
  240. }
  241. impl<R: Runtime> PluginStore<R> {
  242. /// Adds a plugin to the store.
  243. ///
  244. /// Returns `true` if a plugin with the same name is already in the store.
  245. pub fn register<P: Plugin<R> + 'static>(&mut self, plugin: P) -> bool {
  246. self.store.insert(plugin.name(), Box::new(plugin)).is_some()
  247. }
  248. /// Initializes all plugins in the store.
  249. pub(crate) fn initialize(
  250. &mut self,
  251. app: &AppHandle<R>,
  252. config: &PluginConfig,
  253. ) -> crate::Result<()> {
  254. self.store.values_mut().try_for_each(|plugin| {
  255. plugin
  256. .initialize(
  257. app,
  258. config.0.get(plugin.name()).cloned().unwrap_or_default(),
  259. )
  260. .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
  261. })
  262. }
  263. /// Generates an initialization script from all plugins in the store.
  264. pub(crate) fn initialization_script(&self) -> String {
  265. self
  266. .store
  267. .values()
  268. .filter_map(|p| p.initialization_script())
  269. .fold(String::new(), |acc, script| {
  270. format!("{}\n(function () {{ {} }})();", acc, script)
  271. })
  272. }
  273. /// Runs the created hook for all plugins in the store.
  274. pub(crate) fn created(&mut self, window: Window<R>) {
  275. self
  276. .store
  277. .values_mut()
  278. .for_each(|plugin| plugin.created(window.clone()))
  279. }
  280. /// Runs the on_page_load hook for all plugins in the store.
  281. pub(crate) fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
  282. self
  283. .store
  284. .values_mut()
  285. .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
  286. }
  287. /// Runs the on_event hook for all plugins in the store.
  288. pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  289. self
  290. .store
  291. .values_mut()
  292. .for_each(|plugin| plugin.on_event(app, event))
  293. }
  294. pub(crate) fn extend_api(&mut self, mut invoke: Invoke<R>) {
  295. let command = invoke.message.command.replace("plugin:", "");
  296. let mut tokens = command.split('|');
  297. // safe to unwrap: split always has a least one item
  298. let target = tokens.next().unwrap();
  299. if let Some(plugin) = self.store.get_mut(target) {
  300. invoke.message.command = tokens
  301. .next()
  302. .map(|c| c.to_string())
  303. .unwrap_or_else(String::new);
  304. plugin.extend_api(invoke);
  305. } else {
  306. invoke
  307. .resolver
  308. .reject(format!("plugin {} not found", target));
  309. }
  310. }
  311. }