فهرست منبع

feat(tauri) add plugin system for rust (#494)

* feat(tauri) add extension system

* chore(tauri) rename extension to plugin

* chore(tauri) add plugin docs

* chore(tauri) expose WebView type

* chore(changes) add changefile

* fix(tauri) clippy warns

* fix(changes) format

* fix(changes) typo
Lucas Fernandes Nogueira 5 سال پیش
والد
کامیت
78afee9725
5فایلهای تغییر یافته به همراه132 افزوده شده و 20 حذف شده
  1. 5 0
      .changes/plugin-system.md
  2. 6 0
      tauri/src/app.rs
  3. 47 19
      tauri/src/app/runner.rs
  4. 3 1
      tauri/src/lib.rs
  5. 71 0
      tauri/src/plugin.rs

+ 5 - 0
.changes/plugin-system.md

@@ -0,0 +1,5 @@
+---
+"tauri": minor
+---
+
+Plugin system added. You can hook into the webview lifecycle (`created`, `ready`) and extend the API adding logic to the `invoke_handler` by implementing the `tauri::plugin::Plugin` trait.

+ 6 - 0
tauri/src/app.rs

@@ -91,6 +91,12 @@ impl AppBuilder {
     self
   }
 
+  /// Adds a plugin to the runtime.
+  pub fn plugin(self, plugin: impl crate::plugin::Plugin + 'static) -> Self {
+    crate::plugin::register(plugin);
+    self
+  }
+
   /// Builds the App.
   pub fn build(self) -> App {
     App {

+ 47 - 19
tauri/src/app/runner.rs

@@ -30,7 +30,7 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> {
   };
 
   // build the webview
-  let webview = build_webview(
+  let mut webview = build_webview(
     application,
     main_content,
     if application.splashscreen_html().is_some() {
@@ -45,6 +45,8 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> {
     },
   )?;
 
+  crate::plugin::created(&mut webview);
+
   // spawn the embedded server on our server url
   #[cfg(embedded_server)]
   spawn_server(server_url)?;
@@ -217,6 +219,15 @@ fn build_webview(
           "window-1"
         };
         application.run_setup(webview, source.to_string());
+        if source == "window-1" {
+          let handle = webview.handle();
+          handle
+            .dispatch(|webview| {
+              crate::plugin::ready(webview);
+              Ok(())
+            })
+            .expect("failed to invoke ready hook");
+        }
       } else if arg == r#"{"cmd":"closeSplashscreen"}"# {
         let content_href = match content_clone {
           Content::Html(ref html) => html,
@@ -224,26 +235,43 @@ fn build_webview(
         };
         webview.eval(&format!(r#"window.location.href = "{}""#, content_href))?;
       } else {
-        let handler_error;
-        if let Err(tauri_handle_error) = crate::endpoints::handle(webview, arg) {
-          let tauri_handle_error_str = tauri_handle_error.to_string();
-          if tauri_handle_error_str.contains("unknown variant") {
-            let handled_by_app = application.run_invoke_handler(webview, arg);
-            handler_error = if let Err(e) = handled_by_app {
-              Some(e.replace("'", "\\'"))
+        let endpoint_handle = crate::endpoints::handle(webview, arg)
+          .map_err(|tauri_handle_error| {
+            let tauri_handle_error_str = tauri_handle_error.to_string();
+            if tauri_handle_error_str.contains("unknown variant") {
+              match application.run_invoke_handler(webview, arg) {
+                Ok(handled) => {
+                  if handled {
+                    String::from("")
+                  } else {
+                    tauri_handle_error_str
+                  }
+                }
+                Err(e) => e,
+              }
             } else {
-              let handled = handled_by_app.expect("failed to check if the invoke was handled");
-              if handled {
-                None
-              } else {
-                Some(tauri_handle_error_str)
+              tauri_handle_error_str
+            }
+          })
+          .map_err(|app_handle_error| {
+            if app_handle_error.contains("unknown variant") {
+              match crate::plugin::extend_api(webview, arg) {
+                Ok(handled) => {
+                  if handled {
+                    String::from("")
+                  } else {
+                    app_handle_error
+                  }
+                }
+                Err(e) => e,
               }
-            };
-          } else {
-            handler_error = Some(tauri_handle_error_str);
-          }
-
-          if let Some(handler_error_message) = handler_error {
+            } else {
+              app_handle_error
+            }
+          })
+          .map_err(|e| e.replace("'", "\\'"));
+        if let Err(handler_error_message) = endpoint_handle {
+          if handler_error_message != "" {
             webview.eval(&get_api_error_message(arg, handler_error_message))?;
           }
         }

+ 3 - 1
tauri/src/lib.rs

@@ -30,6 +30,8 @@ pub mod cli;
 mod app;
 /// The Tauri API endpoints.
 mod endpoints;
+/// The plugin manager module contains helpers to manage runtime plugins.
+pub mod plugin;
 /// The salt helpers.
 mod salt;
 
@@ -38,13 +40,13 @@ pub use anyhow::Result;
 pub use app::*;
 pub use tauri_api as api;
 pub use web_view::Handle;
+pub use web_view::WebView;
 
 use std::process::Stdio;
 
 use api::rpc::{format_callback, format_callback_result};
 use serde::Serialize;
 use threadpool::ThreadPool;
-use web_view::WebView;
 
 thread_local!(static POOL: ThreadPool = ThreadPool::new(4));
 

+ 71 - 0
tauri/src/plugin.rs

@@ -0,0 +1,71 @@
+use std::sync::{Arc, Mutex};
+use web_view::WebView;
+
+/// The plugin interface.
+pub trait Plugin {
+  /// Callback invoked when the webview is created.
+  #[allow(unused_variables)]
+  fn created(&self, webview: &mut WebView<'_, ()>) {}
+
+  /// Callback invoked when the webview is ready.
+  #[allow(unused_variables)]
+  fn ready(&self, webview: &mut WebView<'_, ()>) {}
+
+  /// Add invoke_handler API extension commands.
+  #[allow(unused_variables)]
+  fn extend_api(&self, webview: &mut WebView<'_, ()>, payload: &str) -> Result<bool, String> {
+    Err("unknown variant".to_string())
+  }
+}
+
+thread_local!(static PLUGINS: Arc<Mutex<Vec<Box<dyn Plugin>>>> = Default::default());
+
+/// Registers a plugin.
+pub fn register(ext: impl Plugin + 'static) {
+  PLUGINS.with(|plugins| {
+    let mut exts = plugins.lock().unwrap();
+    exts.push(Box::new(ext));
+  });
+}
+
+fn run_plugin<T: FnMut(&Box<dyn Plugin>)>(mut callback: T) {
+  PLUGINS.with(|plugins| {
+    let exts = plugins.lock().unwrap();
+    for ext in exts.iter() {
+      callback(ext);
+    }
+  });
+}
+
+pub(crate) fn created(webview: &mut WebView<'_, ()>) {
+  run_plugin(|ext| {
+    ext.created(webview);
+  });
+}
+
+pub(crate) fn ready(webview: &mut WebView<'_, ()>) {
+  run_plugin(|ext| {
+    ext.ready(webview);
+  });
+}
+
+pub(crate) fn extend_api(webview: &mut WebView<'_, ()>, arg: &str) -> Result<bool, String> {
+  PLUGINS.with(|plugins| {
+    let exts = plugins.lock().unwrap();
+    for ext in exts.iter() {
+      match ext.extend_api(webview, arg) {
+        Ok(handled) => {
+          if handled {
+            return Ok(true);
+          }
+        }
+        Err(e) => {
+          if !e.contains("unknown variant") {
+            return Err(e);
+          }
+        }
+      }
+    }
+    Ok(false)
+  })
+}