plugin.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. // Copyright 2019-2022 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. utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, PageLoadPayload, RunEvent,
  7. Runtime, 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. /// Add the provided JavaScript to a list of scripts that should be run after the global object has been created,
  25. /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
  26. ///
  27. /// Since it runs on all top-level document and child frame page navigations,
  28. /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
  29. ///
  30. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  31. /// so global variables must be assigned to `window` instead of implicity declared.
  32. fn initialization_script(&self) -> Option<String> {
  33. None
  34. }
  35. /// Callback invoked when the webview is created.
  36. #[allow(unused_variables)]
  37. fn created(&mut self, window: Window<R>) {}
  38. /// Callback invoked when the webview performs a navigation to a page.
  39. #[allow(unused_variables)]
  40. fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {}
  41. /// Callback invoked when the event loop receives a new event.
  42. #[allow(unused_variables)]
  43. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
  44. /// Extend commands to [`crate::Builder::invoke_handler`].
  45. #[allow(unused_variables)]
  46. fn extend_api(&mut self, invoke: Invoke<R>) {}
  47. }
  48. type SetupHook<R> = dyn FnOnce(&AppHandle<R>) -> Result<()> + Send;
  49. type SetupWithConfigHook<R, T> = dyn FnOnce(&AppHandle<R>, T) -> Result<()> + Send;
  50. type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
  51. type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
  52. type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send;
  53. type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
  54. /// Builds a [`TauriPlugin`].
  55. ///
  56. /// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly.
  57. ///
  58. /// # Conventions
  59. ///
  60. /// When using the Builder Pattern it is encouraged to export a function called `init` that constructs and returns the plugin.
  61. /// While plugin authors can provide every possible way to construct a plugin,
  62. /// sticking to the `init` function convention helps users to quickly identify the correct function to call.
  63. ///
  64. /// ```rust
  65. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  66. ///
  67. /// pub fn init<R: Runtime>() -> TauriPlugin<R> {
  68. /// Builder::new("example")
  69. /// .build()
  70. /// }
  71. /// ```
  72. ///
  73. /// When plugins expose more complex configuration options, it can be helpful to provide a Builder instead:
  74. ///
  75. /// ```rust
  76. /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
  77. ///
  78. /// pub struct Builder {
  79. /// option_a: String,
  80. /// option_b: String,
  81. /// option_c: bool
  82. /// }
  83. ///
  84. /// impl Default for Builder {
  85. /// fn default() -> Self {
  86. /// Self {
  87. /// option_a: "foo".to_string(),
  88. /// option_b: "bar".to_string(),
  89. /// option_c: false
  90. /// }
  91. /// }
  92. /// }
  93. ///
  94. /// impl Builder {
  95. /// pub fn new() -> Self {
  96. /// Default::default()
  97. /// }
  98. ///
  99. /// pub fn option_a(mut self, option_a: String) -> Self {
  100. /// self.option_a = option_a;
  101. /// self
  102. /// }
  103. ///
  104. /// pub fn option_b(mut self, option_b: String) -> Self {
  105. /// self.option_b = option_b;
  106. /// self
  107. /// }
  108. ///
  109. /// pub fn option_c(mut self, option_c: bool) -> Self {
  110. /// self.option_c = option_c;
  111. /// self
  112. /// }
  113. ///
  114. /// pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
  115. /// PluginBuilder::new("example")
  116. /// .setup(move |app_handle| {
  117. /// // use the options here to do stuff
  118. /// println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c);
  119. ///
  120. /// Ok(())
  121. /// })
  122. /// .build()
  123. /// }
  124. /// }
  125. /// ```
  126. pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
  127. name: &'static str,
  128. invoke_handler: Box<InvokeHandler<R>>,
  129. setup: Option<Box<SetupHook<R>>>,
  130. setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
  131. js_init_script: Option<String>,
  132. on_page_load: Box<OnPageLoad<R>>,
  133. on_webview_ready: Box<OnWebviewReady<R>>,
  134. on_event: Box<OnEvent<R>>,
  135. on_drop: Option<Box<OnDrop<R>>>,
  136. }
  137. impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
  138. /// Creates a new Plugin builder.
  139. pub fn new(name: &'static str) -> Self {
  140. Self {
  141. name,
  142. setup: None,
  143. setup_with_config: None,
  144. js_init_script: None,
  145. invoke_handler: Box::new(|_| ()),
  146. on_page_load: Box::new(|_, _| ()),
  147. on_webview_ready: Box::new(|_| ()),
  148. on_event: Box::new(|_, _| ()),
  149. on_drop: None,
  150. }
  151. }
  152. /// Defines the JS message handler callback.
  153. /// 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.
  154. ///
  155. /// # Examples
  156. ///
  157. /// ```rust
  158. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  159. ///
  160. /// #[tauri::command]
  161. /// async fn foobar<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
  162. /// println!("foobar");
  163. ///
  164. /// Ok(())
  165. /// }
  166. ///
  167. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  168. /// Builder::new("example")
  169. /// .invoke_handler(tauri::generate_handler![foobar])
  170. /// .build()
  171. /// }
  172. ///
  173. /// ```
  174. /// [tauri::generate_handler]: ../macro.generate_handler.html
  175. #[must_use]
  176. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  177. where
  178. F: Fn(Invoke<R>) + Send + Sync + 'static,
  179. {
  180. self.invoke_handler = Box::new(invoke_handler);
  181. self
  182. }
  183. /// Sets the provided JavaScript to be run after the global object has been created,
  184. /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
  185. ///
  186. /// Since it runs on all top-level document and child frame page navigations,
  187. /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
  188. ///
  189. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  190. /// so global variables must be assigned to `window` instead of implicity declared.
  191. ///
  192. /// Note that calling this function multiple times overrides previous values.
  193. ///
  194. /// # Examples
  195. ///
  196. /// ```rust
  197. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  198. ///
  199. /// const INIT_SCRIPT: &str = r#"
  200. /// if (window.location.origin === 'https://tauri.app') {
  201. /// console.log("hello world from js init script");
  202. ///
  203. /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
  204. /// }
  205. /// "#;
  206. ///
  207. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  208. /// Builder::new("example")
  209. /// .js_init_script(INIT_SCRIPT.to_string())
  210. /// .build()
  211. /// }
  212. /// ```
  213. #[must_use]
  214. pub fn js_init_script(mut self, js_init_script: String) -> Self {
  215. self.js_init_script = Some(js_init_script);
  216. self
  217. }
  218. /// Define a closure that runs when the plugin is registered.
  219. ///
  220. /// This is a convenience function around [setup_with_config], without the need to specify a configuration object.
  221. ///
  222. /// The closure gets called before the [setup_with_config] closure.
  223. ///
  224. /// # Examples
  225. ///
  226. /// ```rust
  227. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager};
  228. /// use std::path::PathBuf;
  229. ///
  230. /// #[derive(Debug, Default)]
  231. /// struct PluginState {
  232. /// dir: Option<PathBuf>
  233. /// }
  234. ///
  235. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  236. /// Builder::new("example")
  237. /// .setup(|app_handle| {
  238. /// app_handle.manage(PluginState::default());
  239. ///
  240. /// Ok(())
  241. /// })
  242. /// .build()
  243. /// }
  244. /// ```
  245. ///
  246. /// [setup_with_config]: struct.Builder.html#method.setup_with_config
  247. #[must_use]
  248. pub fn setup<F>(mut self, setup: F) -> Self
  249. where
  250. F: FnOnce(&AppHandle<R>) -> Result<()> + Send + 'static,
  251. {
  252. self.setup.replace(Box::new(setup));
  253. self
  254. }
  255. /// Define a closure that runs when the plugin is registered, accepting a configuration object set on `tauri.conf.json > plugins > yourPluginName`.
  256. ///
  257. /// If your plugin is not pulling a configuration object from `tauri.conf.json`, use [setup].
  258. ///
  259. /// The closure gets called after the [setup] closure.
  260. ///
  261. /// # Examples
  262. ///
  263. /// ```rust,no_run
  264. /// #[derive(serde::Deserialize)]
  265. /// struct Config {
  266. /// api_url: String,
  267. /// }
  268. ///
  269. /// fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R, Config> {
  270. /// tauri::plugin::Builder::<R, Config>::new("api")
  271. /// .setup_with_config(|_app_handle, config| {
  272. /// println!("config: {:?}", config.api_url);
  273. /// Ok(())
  274. /// })
  275. /// .build()
  276. /// }
  277. ///
  278. /// tauri::Builder::default().plugin(init());
  279. /// ```
  280. ///
  281. /// [setup]: struct.Builder.html#method.setup
  282. #[must_use]
  283. pub fn setup_with_config<F>(mut self, setup_with_config: F) -> Self
  284. where
  285. F: FnOnce(&AppHandle<R>, C) -> Result<()> + Send + 'static,
  286. {
  287. self.setup_with_config.replace(Box::new(setup_with_config));
  288. self
  289. }
  290. /// Callback invoked when the webview performs a navigation to a page.
  291. ///
  292. /// # Examples
  293. ///
  294. /// ```rust
  295. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  296. ///
  297. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  298. /// Builder::new("example")
  299. /// .on_page_load(|window, payload| {
  300. /// println!("Loaded URL {} in window {}", payload.url(), window.label());
  301. /// })
  302. /// .build()
  303. /// }
  304. /// ```
  305. #[must_use]
  306. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  307. where
  308. F: FnMut(Window<R>, PageLoadPayload) + Send + 'static,
  309. {
  310. self.on_page_load = Box::new(on_page_load);
  311. self
  312. }
  313. /// Callback invoked when the webview is created.
  314. ///
  315. /// # Examples
  316. ///
  317. /// ```rust
  318. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  319. ///
  320. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  321. /// Builder::new("example")
  322. /// .on_webview_ready(|window| {
  323. /// println!("created window {}", window.label());
  324. /// })
  325. /// .build()
  326. /// }
  327. /// ```
  328. #[must_use]
  329. pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
  330. where
  331. F: FnMut(Window<R>) + Send + 'static,
  332. {
  333. self.on_webview_ready = Box::new(on_webview_ready);
  334. self
  335. }
  336. /// Callback invoked when the event loop receives a new event.
  337. ///
  338. /// # Examples
  339. ///
  340. /// ```rust
  341. /// use tauri::{plugin::{Builder, TauriPlugin}, RunEvent, Runtime};
  342. ///
  343. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  344. /// Builder::new("example")
  345. /// .on_event(|app_handle, event| {
  346. /// match event {
  347. /// RunEvent::ExitRequested { api, .. } => {
  348. /// // Prevents the app from exiting.
  349. /// // This will cause the core thread to continue running in the background even without any open windows.
  350. /// api.prevent_exit();
  351. /// }
  352. /// // Ignore all other cases.
  353. /// _ => {}
  354. /// }
  355. /// })
  356. /// .build()
  357. /// }
  358. /// ```
  359. #[must_use]
  360. pub fn on_event<F>(mut self, on_event: F) -> Self
  361. where
  362. F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,
  363. {
  364. self.on_event = Box::new(on_event);
  365. self
  366. }
  367. /// Callback invoked when the plugin is dropped.
  368. ///
  369. /// # Examples
  370. ///
  371. /// ```rust
  372. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  373. ///
  374. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  375. /// Builder::new("example")
  376. /// .on_drop(|app| {
  377. /// println!("plugin has been dropped and is no longer running");
  378. /// // you can run cleanup logic here
  379. /// })
  380. /// .build()
  381. /// }
  382. /// ```
  383. #[must_use]
  384. pub fn on_drop<F>(mut self, on_drop: F) -> Self
  385. where
  386. F: FnOnce(AppHandle<R>) + Send + 'static,
  387. {
  388. self.on_drop.replace(Box::new(on_drop));
  389. self
  390. }
  391. /// Builds the [TauriPlugin].
  392. pub fn build(self) -> TauriPlugin<R, C> {
  393. TauriPlugin {
  394. name: self.name,
  395. app: None,
  396. invoke_handler: self.invoke_handler,
  397. setup: self.setup,
  398. setup_with_config: self.setup_with_config,
  399. js_init_script: self.js_init_script,
  400. on_page_load: self.on_page_load,
  401. on_webview_ready: self.on_webview_ready,
  402. on_event: self.on_event,
  403. on_drop: self.on_drop,
  404. }
  405. }
  406. }
  407. /// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
  408. pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
  409. name: &'static str,
  410. app: Option<AppHandle<R>>,
  411. invoke_handler: Box<InvokeHandler<R>>,
  412. setup: Option<Box<SetupHook<R>>>,
  413. setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
  414. js_init_script: Option<String>,
  415. on_page_load: Box<OnPageLoad<R>>,
  416. on_webview_ready: Box<OnWebviewReady<R>>,
  417. on_event: Box<OnEvent<R>>,
  418. on_drop: Option<Box<OnDrop<R>>>,
  419. }
  420. impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
  421. fn drop(&mut self) {
  422. if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {
  423. on_drop(app);
  424. }
  425. }
  426. }
  427. impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
  428. fn name(&self) -> &'static str {
  429. self.name
  430. }
  431. fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
  432. self.app.replace(app.clone());
  433. if let Some(s) = self.setup.take() {
  434. (s)(app)?;
  435. }
  436. if let Some(s) = self.setup_with_config.take() {
  437. (s)(app, serde_json::from_value(config)?)?;
  438. }
  439. Ok(())
  440. }
  441. fn initialization_script(&self) -> Option<String> {
  442. self.js_init_script.clone()
  443. }
  444. fn created(&mut self, window: Window<R>) {
  445. (self.on_webview_ready)(window)
  446. }
  447. fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
  448. (self.on_page_load)(window, payload)
  449. }
  450. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  451. (self.on_event)(app, event)
  452. }
  453. fn extend_api(&mut self, invoke: Invoke<R>) {
  454. (self.invoke_handler)(invoke)
  455. }
  456. }
  457. /// Plugin collection type.
  458. #[default_runtime(crate::Wry, wry)]
  459. pub(crate) struct PluginStore<R: Runtime> {
  460. store: HashMap<&'static str, Box<dyn Plugin<R>>>,
  461. }
  462. impl<R: Runtime> fmt::Debug for PluginStore<R> {
  463. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  464. f.debug_struct("PluginStore")
  465. .field("plugins", &self.store.keys())
  466. .finish()
  467. }
  468. }
  469. impl<R: Runtime> Default for PluginStore<R> {
  470. fn default() -> Self {
  471. Self {
  472. store: HashMap::new(),
  473. }
  474. }
  475. }
  476. impl<R: Runtime> PluginStore<R> {
  477. /// Adds a plugin to the store.
  478. ///
  479. /// Returns `true` if a plugin with the same name is already in the store.
  480. pub fn register<P: Plugin<R> + 'static>(&mut self, plugin: P) -> bool {
  481. self.store.insert(plugin.name(), Box::new(plugin)).is_some()
  482. }
  483. /// Removes the plugin with the given name from the store.
  484. pub fn unregister(&mut self, plugin: &'static str) -> bool {
  485. self.store.remove(plugin).is_some()
  486. }
  487. /// Initializes all plugins in the store.
  488. pub(crate) fn initialize(
  489. &mut self,
  490. app: &AppHandle<R>,
  491. config: &PluginConfig,
  492. ) -> crate::Result<()> {
  493. self.store.values_mut().try_for_each(|plugin| {
  494. plugin
  495. .initialize(
  496. app,
  497. config.0.get(plugin.name()).cloned().unwrap_or_default(),
  498. )
  499. .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
  500. })
  501. }
  502. /// Generates an initialization script from all plugins in the store.
  503. pub(crate) fn initialization_script(&self) -> String {
  504. self
  505. .store
  506. .values()
  507. .filter_map(|p| p.initialization_script())
  508. .fold(String::new(), |acc, script| {
  509. format!("{acc}\n(function () {{ {script} }})();")
  510. })
  511. }
  512. /// Runs the created hook for all plugins in the store.
  513. pub(crate) fn created(&mut self, window: Window<R>) {
  514. self
  515. .store
  516. .values_mut()
  517. .for_each(|plugin| plugin.created(window.clone()))
  518. }
  519. /// Runs the on_page_load hook for all plugins in the store.
  520. pub(crate) fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
  521. self
  522. .store
  523. .values_mut()
  524. .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
  525. }
  526. /// Runs the on_event hook for all plugins in the store.
  527. pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  528. self
  529. .store
  530. .values_mut()
  531. .for_each(|plugin| plugin.on_event(app, event))
  532. }
  533. pub(crate) fn extend_api(&mut self, mut invoke: Invoke<R>) {
  534. let command = invoke.message.command.replace("plugin:", "");
  535. let mut tokens = command.split('|');
  536. // safe to unwrap: split always has a least one item
  537. let target = tokens.next().unwrap();
  538. if let Some(plugin) = self.store.get_mut(target) {
  539. invoke.message.command = tokens
  540. .next()
  541. .map(|c| c.to_string())
  542. .unwrap_or_else(String::new);
  543. plugin.extend_api(invoke);
  544. } else {
  545. invoke.resolver.reject(format!("plugin {target} not found"));
  546. }
  547. }
  548. }