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
This commit is contained in:
Lucas Fernandes Nogueira 2020-07-12 19:36:11 -03:00 committed by GitHub
parent 660a2d87d6
commit 78afee9725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 21 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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("'", "\\'"))
} else {
let handled = handled_by_app.expect("failed to check if the invoke was handled");
if handled {
None
} else {
Some(tauri_handle_error_str)
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 {
handler_error = Some(tauri_handle_error_str);
}
if let Some(handler_error_message) = handler_error {
} else {
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 {
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))?;
}
}

View File

@ -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
tauri/src/plugin.rs Normal file
View File

@ -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)
})
}