Skip to content

Commit 78afee9

Browse files
authored
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
1 parent 660a2d8 commit 78afee9

5 files changed

Lines changed: 132 additions & 20 deletions

File tree

.changes/plugin-system.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": minor
3+
---
4+
5+
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.

tauri/src/app.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ impl AppBuilder {
9191
self
9292
}
9393

94+
/// Adds a plugin to the runtime.
95+
pub fn plugin(self, plugin: impl crate::plugin::Plugin + 'static) -> Self {
96+
crate::plugin::register(plugin);
97+
self
98+
}
99+
94100
/// Builds the App.
95101
pub fn build(self) -> App {
96102
App {

tauri/src/app/runner.rs

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> {
3030
};
3131

3232
// build the webview
33-
let webview = build_webview(
33+
let mut webview = build_webview(
3434
application,
3535
main_content,
3636
if application.splashscreen_html().is_some() {
@@ -45,6 +45,8 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> {
4545
},
4646
)?;
4747

48+
crate::plugin::created(&mut webview);
49+
4850
// spawn the embedded server on our server url
4951
#[cfg(embedded_server)]
5052
spawn_server(server_url)?;
@@ -217,33 +219,59 @@ fn build_webview(
217219
"window-1"
218220
};
219221
application.run_setup(webview, source.to_string());
222+
if source == "window-1" {
223+
let handle = webview.handle();
224+
handle
225+
.dispatch(|webview| {
226+
crate::plugin::ready(webview);
227+
Ok(())
228+
})
229+
.expect("failed to invoke ready hook");
230+
}
220231
} else if arg == r#"{"cmd":"closeSplashscreen"}"# {
221232
let content_href = match content_clone {
222233
Content::Html(ref html) => html,
223234
Content::Url(ref url) => url,
224235
};
225236
webview.eval(&format!(r#"window.location.href = "{}""#, content_href))?;
226237
} else {
227-
let handler_error;
228-
if let Err(tauri_handle_error) = crate::endpoints::handle(webview, arg) {
229-
let tauri_handle_error_str = tauri_handle_error.to_string();
230-
if tauri_handle_error_str.contains("unknown variant") {
231-
let handled_by_app = application.run_invoke_handler(webview, arg);
232-
handler_error = if let Err(e) = handled_by_app {
233-
Some(e.replace("'", "\\'"))
238+
let endpoint_handle = crate::endpoints::handle(webview, arg)
239+
.map_err(|tauri_handle_error| {
240+
let tauri_handle_error_str = tauri_handle_error.to_string();
241+
if tauri_handle_error_str.contains("unknown variant") {
242+
match application.run_invoke_handler(webview, arg) {
243+
Ok(handled) => {
244+
if handled {
245+
String::from("")
246+
} else {
247+
tauri_handle_error_str
248+
}
249+
}
250+
Err(e) => e,
251+
}
234252
} else {
235-
let handled = handled_by_app.expect("failed to check if the invoke was handled");
236-
if handled {
237-
None
238-
} else {
239-
Some(tauri_handle_error_str)
253+
tauri_handle_error_str
254+
}
255+
})
256+
.map_err(|app_handle_error| {
257+
if app_handle_error.contains("unknown variant") {
258+
match crate::plugin::extend_api(webview, arg) {
259+
Ok(handled) => {
260+
if handled {
261+
String::from("")
262+
} else {
263+
app_handle_error
264+
}
265+
}
266+
Err(e) => e,
240267
}
241-
};
242-
} else {
243-
handler_error = Some(tauri_handle_error_str);
244-
}
245-
246-
if let Some(handler_error_message) = handler_error {
268+
} else {
269+
app_handle_error
270+
}
271+
})
272+
.map_err(|e| e.replace("'", "\\'"));
273+
if let Err(handler_error_message) = endpoint_handle {
274+
if handler_error_message != "" {
247275
webview.eval(&get_api_error_message(arg, handler_error_message))?;
248276
}
249277
}

tauri/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pub mod cli;
3030
mod app;
3131
/// The Tauri API endpoints.
3232
mod endpoints;
33+
/// The plugin manager module contains helpers to manage runtime plugins.
34+
pub mod plugin;
3335
/// The salt helpers.
3436
mod salt;
3537

@@ -38,13 +40,13 @@ pub use anyhow::Result;
3840
pub use app::*;
3941
pub use tauri_api as api;
4042
pub use web_view::Handle;
43+
pub use web_view::WebView;
4144

4245
use std::process::Stdio;
4346

4447
use api::rpc::{format_callback, format_callback_result};
4548
use serde::Serialize;
4649
use threadpool::ThreadPool;
47-
use web_view::WebView;
4850

4951
thread_local!(static POOL: ThreadPool = ThreadPool::new(4));
5052

tauri/src/plugin.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::sync::{Arc, Mutex};
2+
use web_view::WebView;
3+
4+
/// The plugin interface.
5+
pub trait Plugin {
6+
/// Callback invoked when the webview is created.
7+
#[allow(unused_variables)]
8+
fn created(&self, webview: &mut WebView<'_, ()>) {}
9+
10+
/// Callback invoked when the webview is ready.
11+
#[allow(unused_variables)]
12+
fn ready(&self, webview: &mut WebView<'_, ()>) {}
13+
14+
/// Add invoke_handler API extension commands.
15+
#[allow(unused_variables)]
16+
fn extend_api(&self, webview: &mut WebView<'_, ()>, payload: &str) -> Result<bool, String> {
17+
Err("unknown variant".to_string())
18+
}
19+
}
20+
21+
thread_local!(static PLUGINS: Arc<Mutex<Vec<Box<dyn Plugin>>>> = Default::default());
22+
23+
/// Registers a plugin.
24+
pub fn register(ext: impl Plugin + 'static) {
25+
PLUGINS.with(|plugins| {
26+
let mut exts = plugins.lock().unwrap();
27+
exts.push(Box::new(ext));
28+
});
29+
}
30+
31+
fn run_plugin<T: FnMut(&Box<dyn Plugin>)>(mut callback: T) {
32+
PLUGINS.with(|plugins| {
33+
let exts = plugins.lock().unwrap();
34+
for ext in exts.iter() {
35+
callback(ext);
36+
}
37+
});
38+
}
39+
40+
pub(crate) fn created(webview: &mut WebView<'_, ()>) {
41+
run_plugin(|ext| {
42+
ext.created(webview);
43+
});
44+
}
45+
46+
pub(crate) fn ready(webview: &mut WebView<'_, ()>) {
47+
run_plugin(|ext| {
48+
ext.ready(webview);
49+
});
50+
}
51+
52+
pub(crate) fn extend_api(webview: &mut WebView<'_, ()>, arg: &str) -> Result<bool, String> {
53+
PLUGINS.with(|plugins| {
54+
let exts = plugins.lock().unwrap();
55+
for ext in exts.iter() {
56+
match ext.extend_api(webview, arg) {
57+
Ok(handled) => {
58+
if handled {
59+
return Ok(true);
60+
}
61+
}
62+
Err(e) => {
63+
if !e.contains("unknown variant") {
64+
return Err(e);
65+
}
66+
}
67+
}
68+
}
69+
Ok(false)
70+
})
71+
}

0 commit comments

Comments
 (0)