diff --git a/.changes/plugin-setup-refactor.md b/.changes/plugin-setup-refactor.md new file mode 100644 index 000000000..d68597a32 --- /dev/null +++ b/.changes/plugin-setup-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": major +--- + +Changed the plugin setup hook to take a second argument of type `PluginApi`. diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index ffcf17f2b..687b2952c 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -383,90 +383,6 @@ impl AppHandle { pub(crate) fn create_proxy(&self) -> R::EventLoopProxy { self.runtime_handle.create_proxy() } - - /// Initializes an iOS plugin. - #[cfg(target_os = "ios")] - pub fn initialize_ios_plugin( - &self, - init_fn: unsafe extern "C" fn(cocoa::base::id), - ) -> crate::Result<()> { - if let Some(window) = self.windows().values().next() { - window.with_webview(move |w| { - unsafe { init_fn(w.inner()) }; - })?; - } else { - unsafe { init_fn(cocoa::base::nil) }; - } - Ok(()) - } - - /// Initializes an Android plugin. - #[cfg(target_os = "android")] - pub fn initialize_android_plugin( - &self, - plugin_name: &'static str, - plugin_identifier: &str, - class_name: &str, - ) -> crate::Result<()> { - use jni::{errors::Error as JniError, objects::JObject, JNIEnv}; - - fn initialize_plugin<'a, R: Runtime>( - env: JNIEnv<'a>, - activity: JObject<'a>, - webview: JObject<'a>, - runtime_handle: &R::Handle, - plugin_name: &'static str, - plugin_class: String, - ) -> Result<(), JniError> { - let plugin_manager = env - .call_method( - activity, - "getPluginManager", - format!("()Lapp/tauri/plugin/PluginManager;"), - &[], - )? - .l()?; - - // instantiate plugin - let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?; - let plugin = env.new_object( - plugin_class, - "(Landroid/app/Activity;)V", - &[activity.into()], - )?; - - // load plugin - env.call_method( - plugin_manager, - "load", - format!("(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;)V"), - &[ - webview.into(), - env.new_string(plugin_name)?.into(), - plugin.into(), - ], - )?; - - Ok(()) - } - - let plugin_class = format!("{}/{}", plugin_identifier.replace(".", "/"), class_name); - let runtime_handle = self.runtime_handle.clone(); - self - .runtime_handle - .run_on_android_context(move |env, activity, webview| { - let _ = initialize_plugin::( - env, - activity, - webview, - &runtime_handle, - plugin_name, - plugin_class, - ); - }); - - Ok(()) - } } /// APIs specific to the wry runtime. @@ -876,156 +792,6 @@ macro_rules! shared_app_impl { } Ok(()) } - - /// Executes the given plugin mobile method. - #[cfg(mobile)] - pub fn run_mobile_plugin( - &self, - plugin: impl AsRef, - method: impl AsRef, - payload: impl serde::Serialize - ) -> crate::Result> { - #[cfg(target_os = "ios")] - { - Ok(self.run_ios_plugin(plugin, method, payload)) - } - #[cfg(target_os = "android")] - { - self.run_android_plugin(plugin, method, payload).map_err(Into::into) - } - } - - /// Executes the given iOS plugin method. - #[cfg(target_os = "ios")] - fn run_ios_plugin( - &self, - plugin: impl AsRef, - method: impl AsRef, - payload: impl serde::Serialize - ) -> Result { - use std::{os::raw::{c_int, c_char}, ffi::CStr, sync::mpsc::channel}; - - let id: i32 = rand::random(); - let (tx, rx) = channel(); - PENDING_PLUGIN_CALLS - .get_or_init(Default::default) - .lock() - .unwrap().insert(id, Box::new(move |arg| { - tx.send(arg).unwrap(); - })); - - unsafe { - extern "C" fn plugin_method_response_handler(id: c_int, success: c_int, payload: *const c_char) { - let payload = unsafe { - assert!(!payload.is_null()); - CStr::from_ptr(payload) - }; - - if let Some(handler) = PENDING_PLUGIN_CALLS - .get_or_init(Default::default) - .lock() - .unwrap() - .remove(&id) - { - let payload = serde_json::from_str(payload.to_str().unwrap()).unwrap(); - handler(if success == 1 { Ok(payload) } else { Err(payload) }); - } - } - - crate::ios::run_plugin_method( - id, - &plugin.as_ref().into(), - &method.as_ref().into(), - crate::ios::json_to_dictionary(serde_json::to_value(payload).unwrap()), - plugin_method_response_handler, - ); - } - rx.recv().unwrap() - .map(|r| serde_json::from_value(r).unwrap()) - .map_err(|e| serde_json::from_value(e).unwrap()) - } - - /// Executes the given Android plugin method. - #[cfg(target_os = "android")] - fn run_android_plugin( - &self, - plugin: impl AsRef, - method: impl AsRef, - payload: impl serde::Serialize - ) -> Result, jni::errors::Error> { - use jni::{ - errors::Error as JniError, - objects::JObject, - JNIEnv, - }; - - fn run( - id: i32, - plugin: String, - method: String, - payload: serde_json::Value, - runtime_handle: &R::Handle, - env: JNIEnv<'_>, - activity: JObject<'_>, - ) -> Result<(), JniError> { - let data = crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, payload)?; - let plugin_manager = env - .call_method( - activity, - "getPluginManager", - "()Lapp/tauri/plugin/PluginManager;", - &[], - )? - .l()?; - - env.call_method( - plugin_manager, - "runPluginMethod", - "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V", - &[ - id.into(), - env.new_string(plugin)?.into(), - env.new_string(&method)?.into(), - data.into(), - ], - )?; - - Ok(()) - } - - let handle = match self.runtime() { - RuntimeOrDispatch::Runtime(r) => r.handle(), - RuntimeOrDispatch::RuntimeHandle(h) => h, - _ => unreachable!(), - }; - - let id: i32 = rand::random(); - let plugin = plugin.as_ref().to_string(); - let method = method.as_ref().to_string(); - let payload = serde_json::to_value(payload).unwrap(); - let handle_ = handle.clone(); - - let (tx, rx) = std::sync::mpsc::channel(); - let tx_ = tx.clone(); - PENDING_PLUGIN_CALLS - .get_or_init(Default::default) - .lock() - .unwrap().insert(id, Box::new(move |arg| { - tx.send(Ok(arg)).unwrap(); - })); - - handle.run_on_android_context(move |env, activity, _webview| { - if let Err(e) = run::(id, plugin, method, payload, &handle_, env, activity) { - tx_.send(Err(e)).unwrap(); - } - }); - - rx.recv().unwrap().map(|response| { - response - .map(|r| serde_json::from_value(r).unwrap()) - .map_err(|e| serde_json::from_value(e).unwrap()) - }) - } } }; } @@ -1443,7 +1209,7 @@ impl Builder { /// } /// pub fn init() -> TauriPlugin { /// PluginBuilder::new("window") - /// .setup(|app| { + /// .setup(|app, api| { /// // initialize the plugin here /// Ok(()) /// }) @@ -2094,57 +1860,6 @@ impl Default for Builder { } } -#[cfg(mobile)] -type PendingPluginCallHandler = - Box) + Send + 'static>; - -#[cfg(mobile)] -static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell< - std::sync::Mutex>, -> = once_cell::sync::OnceCell::new(); - -#[cfg(target_os = "android")] -#[doc(hidden)] -pub fn handle_android_plugin_response( - env: jni::JNIEnv<'_>, - id: i32, - success: jni::objects::JString<'_>, - error: jni::objects::JString<'_>, -) { - let (payload, is_ok): (serde_json::Value, bool) = match ( - env - .is_same_object(success, jni::objects::JObject::default()) - .unwrap_or_default(), - env - .is_same_object(error, jni::objects::JObject::default()) - .unwrap_or_default(), - ) { - // both null - (true, true) => (serde_json::Value::Null, true), - // error null - (false, true) => ( - serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(), - true, - ), - // success null - (true, false) => ( - serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(), - false, - ), - // both are set - impossible in the Kotlin code - (false, false) => unreachable!(), - }; - - if let Some(handler) = PENDING_PLUGIN_CALLS - .get_or_init(Default::default) - .lock() - .unwrap() - .remove(&id) - { - handler(if is_ok { Ok(payload) } else { Err(payload) }); - } -} - #[cfg(test)] mod tests { #[test] diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 46f5e1f97..f2de93d52 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -240,7 +240,7 @@ macro_rules! android_binding { #[cfg(all(feature = "wry", target_os = "android"))] #[doc(hidden)] -pub use app::handle_android_plugin_response; +pub use plugin::handle_android_plugin_response; #[cfg(all(feature = "wry", target_os = "android"))] #[doc(hidden)] pub use tauri_runtime_wry::wry; diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index eb3dc3884..63fcff01b 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -4,6 +4,11 @@ //! The Tauri plugin extension to expand Tauri functionality. +#[cfg(target_os = "android")] +use crate::{ + runtime::RuntimeHandle, + sealed::{ManagerBase, RuntimeOrDispatch}, +}; use crate::{ utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, PageLoadPayload, RunEvent, Runtime, Window, @@ -12,10 +17,10 @@ use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; use tauri_macros::default_runtime; -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt, result::Result as StdResult}; /// The result type of Tauri plugin module. -pub type Result = std::result::Result>; +pub type Result = StdResult>; /// The plugin interface. pub trait Plugin: Send { @@ -59,13 +64,344 @@ pub trait Plugin: Send { } } -type SetupHook = dyn FnOnce(&AppHandle) -> Result<()> + Send; -type SetupWithConfigHook = dyn FnOnce(&AppHandle, T) -> Result<()> + Send; +type SetupHook = dyn FnOnce(&AppHandle, PluginApi) -> Result<()> + Send; type OnWebviewReady = dyn FnMut(Window) + Send; type OnEvent = dyn FnMut(&AppHandle, &RunEvent) + Send; type OnPageLoad = dyn FnMut(Window, PageLoadPayload) + Send; type OnDrop = dyn FnOnce(AppHandle) + Send; +#[cfg(mobile)] +type PendingPluginCallHandler = + Box) + Send + 'static>; + +#[cfg(mobile)] +static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell< + std::sync::Mutex>, +> = once_cell::sync::OnceCell::new(); + +#[cfg(target_os = "android")] +#[doc(hidden)] +pub fn handle_android_plugin_response( + env: jni::JNIEnv<'_>, + id: i32, + success: jni::objects::JString<'_>, + error: jni::objects::JString<'_>, +) { + let (payload, is_ok): (serde_json::Value, bool) = match ( + env + .is_same_object(success, jni::objects::JObject::default()) + .unwrap_or_default(), + env + .is_same_object(error, jni::objects::JObject::default()) + .unwrap_or_default(), + ) { + // both null + (true, true) => (serde_json::Value::Null, true), + // error null + (false, true) => ( + serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(), + true, + ), + // success null + (true, false) => ( + serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(), + false, + ), + // both are set - impossible in the Kotlin code + (false, false) => unreachable!(), + }; + + if let Some(handler) = PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .remove(&id) + { + handler(if is_ok { Ok(payload) } else { Err(payload) }); + } +} + +/// A handle to a plugin. +#[derive(Clone)] +#[allow(dead_code)] +pub struct PluginHandle { + name: &'static str, + handle: AppHandle, +} + +impl PluginHandle { + /// Executes the given mobile method. + #[cfg(mobile)] + pub fn run_mobile_plugin( + &self, + method: impl AsRef, + payload: impl serde::Serialize, + ) -> crate::Result> { + #[cfg(target_os = "ios")] + { + Ok(self.run_ios_plugin(method, payload)) + } + #[cfg(target_os = "android")] + { + self.run_android_plugin(method, payload).map_err(Into::into) + } + } + + /// Executes the given iOS method. + #[cfg(target_os = "ios")] + fn run_ios_plugin( + &self, + method: impl AsRef, + payload: impl serde::Serialize, + ) -> StdResult { + use std::{ + ffi::CStr, + os::raw::{c_char, c_int}, + sync::mpsc::channel, + }; + + let id: i32 = rand::random(); + let (tx, rx) = channel(); + PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .insert( + id, + Box::new(move |arg| { + tx.send(arg).unwrap(); + }), + ); + + unsafe { + extern "C" fn plugin_method_response_handler( + id: c_int, + success: c_int, + payload: *const c_char, + ) { + let payload = unsafe { + assert!(!payload.is_null()); + CStr::from_ptr(payload) + }; + + if let Some(handler) = PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .remove(&id) + { + let payload = serde_json::from_str(payload.to_str().unwrap()).unwrap(); + handler(if success == 1 { + Ok(payload) + } else { + Err(payload) + }); + } + } + + crate::ios::run_plugin_method( + id, + &self.name.into(), + &method.as_ref().into(), + crate::ios::json_to_dictionary(serde_json::to_value(payload).unwrap()), + plugin_method_response_handler, + ); + } + rx.recv() + .unwrap() + .map(|r| serde_json::from_value(r).unwrap()) + .map_err(|e| serde_json::from_value(e).unwrap()) + } + + /// Executes the given Android method. + #[cfg(target_os = "android")] + fn run_android_plugin( + &self, + method: impl AsRef, + payload: impl serde::Serialize, + ) -> StdResult, jni::errors::Error> { + use jni::{errors::Error as JniError, objects::JObject, JNIEnv}; + + fn run( + id: i32, + plugin: &'static str, + method: String, + payload: serde_json::Value, + runtime_handle: &R::Handle, + env: JNIEnv<'_>, + activity: JObject<'_>, + ) -> StdResult<(), JniError> { + let data = crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, payload)?; + let plugin_manager = env + .call_method( + activity, + "getPluginManager", + "()Lapp/tauri/plugin/PluginManager;", + &[], + )? + .l()?; + + env.call_method( + plugin_manager, + "runPluginMethod", + "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V", + &[ + id.into(), + env.new_string(plugin)?.into(), + env.new_string(&method)?.into(), + data.into(), + ], + )?; + + Ok(()) + } + + let handle = match self.handle.runtime() { + RuntimeOrDispatch::Runtime(r) => r.handle(), + RuntimeOrDispatch::RuntimeHandle(h) => h, + _ => unreachable!(), + }; + + let id: i32 = rand::random(); + let plugin_name = self.name; + let method = method.as_ref().to_string(); + let payload = serde_json::to_value(payload).unwrap(); + let handle_ = handle.clone(); + + let (tx, rx) = std::sync::mpsc::channel(); + let tx_ = tx.clone(); + PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .insert( + id, + Box::new(move |arg| { + tx.send(Ok(arg)).unwrap(); + }), + ); + + handle.run_on_android_context(move |env, activity, _webview| { + if let Err(e) = run::(id, plugin_name, method, payload, &handle_, env, activity) { + tx_.send(Err(e)).unwrap(); + } + }); + + rx.recv().unwrap().map(|response| { + response + .map(|r| serde_json::from_value(r).unwrap()) + .map_err(|e| serde_json::from_value(e).unwrap()) + }) + } +} + +/// Api exposed to the plugin setup hook. +#[derive(Clone)] +#[allow(dead_code)] +pub struct PluginApi { + handle: AppHandle, + name: &'static str, + config: C, +} + +impl PluginApi { + /// Returns the plugin configuration. + pub fn config(&self) -> &C { + &self.config + } + + /// Registers an iOS plugin. + #[cfg(target_os = "ios")] + pub fn register_ios_plugin( + &self, + init_fn: unsafe extern "C" fn(cocoa::base::id), + ) -> crate::Result> { + if let Some(window) = self.handle.manager.windows().values().next() { + window.with_webview(move |w| { + unsafe { init_fn(w.inner()) }; + })?; + } else { + unsafe { init_fn(cocoa::base::nil) }; + } + Ok(PluginHandle { + name: self.name, + handle: self.handle.clone(), + }) + } + + /// Registers an Android plugin. + #[cfg(target_os = "android")] + pub fn register_android_plugin( + &self, + plugin_identifier: &str, + class_name: &str, + ) -> crate::Result> { + use jni::{errors::Error as JniError, objects::JObject, JNIEnv}; + + fn initialize_plugin<'a, R: Runtime>( + env: JNIEnv<'a>, + activity: JObject<'a>, + webview: JObject<'a>, + runtime_handle: &R::Handle, + plugin_name: &'static str, + plugin_class: String, + ) -> StdResult<(), JniError> { + let plugin_manager = env + .call_method( + activity, + "getPluginManager", + format!("()Lapp/tauri/plugin/PluginManager;"), + &[], + )? + .l()?; + + // instantiate plugin + let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?; + let plugin = env.new_object( + plugin_class, + "(Landroid/app/Activity;)V", + &[activity.into()], + )?; + + // load plugin + env.call_method( + plugin_manager, + "load", + format!("(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;)V"), + &[ + webview.into(), + env.new_string(plugin_name)?.into(), + plugin.into(), + ], + )?; + + Ok(()) + } + + let plugin_class = format!("{}/{}", plugin_identifier.replace(".", "/"), class_name); + let plugin_name = self.name; + let runtime_handle = self.handle.runtime_handle.clone(); + self + .handle + .runtime_handle + .run_on_android_context(move |env, activity, webview| { + let _ = initialize_plugin::( + env, + activity, + webview, + &runtime_handle, + plugin_name, + plugin_class, + ); + }); + + Ok(PluginHandle { + name: self.name, + handle: self.handle.clone(), + }) + } +} + /// Builds a [`TauriPlugin`]. /// /// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly. @@ -128,7 +464,7 @@ type OnDrop = dyn FnOnce(AppHandle) + Send; /// /// pub fn build(self) -> TauriPlugin { /// PluginBuilder::new("example") -/// .setup(move |app_handle| { +/// .setup(move |app_handle, api| { /// // use the options here to do stuff /// println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c); /// @@ -141,8 +477,7 @@ type OnDrop = dyn FnOnce(AppHandle) + Send; pub struct Builder { name: &'static str, invoke_handler: Box>, - setup: Option>>, - setup_with_config: Option>>, + setup: Option>>, js_init_script: Option, on_page_load: Box>, on_webview_ready: Box>, @@ -156,7 +491,6 @@ impl Builder { Self { name, setup: None, - setup_with_config: None, js_init_script: None, invoke_handler: Box::new(|_| false), on_page_load: Box::new(|_, _| ()), @@ -236,10 +570,6 @@ impl Builder { /// Define a closure that runs when the plugin is registered. /// - /// This is a convenience function around [setup_with_config], without the need to specify a configuration object. - /// - /// The closure gets called before the [setup_with_config] closure. - /// /// # Examples /// /// ```rust @@ -253,61 +583,23 @@ impl Builder { /// /// fn init() -> TauriPlugin { /// Builder::new("example") - /// .setup(|app_handle| { - /// app_handle.manage(PluginState::default()); + /// .setup(|app, api| { + /// app.manage(PluginState::default()); /// /// Ok(()) /// }) /// .build() /// } /// ``` - /// - /// [setup_with_config]: struct.Builder.html#method.setup_with_config #[must_use] pub fn setup(mut self, setup: F) -> Self where - F: FnOnce(&AppHandle) -> Result<()> + Send + 'static, + F: FnOnce(&AppHandle, PluginApi) -> Result<()> + Send + 'static, { self.setup.replace(Box::new(setup)); self } - /// Define a closure that runs when the plugin is registered, accepting a configuration object set on `tauri.conf.json > plugins > yourPluginName`. - /// - /// If your plugin is not pulling a configuration object from `tauri.conf.json`, use [setup]. - /// - /// The closure gets called after the [setup] closure. - /// - /// # Examples - /// - /// ```rust,no_run - /// #[derive(serde::Deserialize)] - /// struct Config { - /// api_url: String, - /// } - /// - /// fn init() -> tauri::plugin::TauriPlugin { - /// tauri::plugin::Builder::::new("api") - /// .setup_with_config(|_app_handle, config| { - /// println!("config: {:?}", config.api_url); - /// Ok(()) - /// }) - /// .build() - /// } - /// - /// tauri::Builder::default().plugin(init()); - /// ``` - /// - /// [setup]: struct.Builder.html#method.setup - #[must_use] - pub fn setup_with_config(mut self, setup_with_config: F) -> Self - where - F: FnOnce(&AppHandle, C) -> Result<()> + Send + 'static, - { - self.setup_with_config.replace(Box::new(setup_with_config)); - self - } - /// Callback invoked when the webview performs a navigation to a page. /// /// # Examples @@ -420,7 +712,6 @@ impl Builder { app: None, invoke_handler: self.invoke_handler, setup: self.setup, - setup_with_config: self.setup_with_config, js_init_script: self.js_init_script, on_page_load: self.on_page_load, on_webview_ready: self.on_webview_ready, @@ -435,8 +726,7 @@ pub struct TauriPlugin { name: &'static str, app: Option>, invoke_handler: Box>, - setup: Option>>, - setup_with_config: Option>>, + setup: Option>>, js_init_script: Option, on_page_load: Box>, on_webview_ready: Box>, @@ -460,10 +750,14 @@ impl Plugin for TauriPlugin { fn initialize(&mut self, app: &AppHandle, config: JsonValue) -> Result<()> { self.app.replace(app.clone()); if let Some(s) = self.setup.take() { - (s)(app)?; - } - if let Some(s) = self.setup_with_config.take() { - (s)(app, serde_json::from_value(config)?)?; + (s)( + app, + PluginApi { + name: self.name, + handle: app.clone(), + config: serde_json::from_value(config)?, + }, + )?; } Ok(()) } diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 974c93a0c..15754daf6 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -95,16 +95,6 @@ impl AppBuilder { #[cfg(debug_assertions)] window.open_devtools(); - #[cfg(mobile)] - { - let response = app.run_mobile_plugin::( - "sample", - "ping", - serde_json::Value::default(), - ); - println!("got response: {:?}", response); - } - #[cfg(desktop)] std::thread::spawn(|| { let server = match tiny_http::Server::http("localhost:3003") { diff --git a/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs b/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs index 5ffb869bd..457633cbe 100644 --- a/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs +++ b/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs @@ -1,9 +1,8 @@ use tauri::{ - plugin::{Builder, TauriPlugin}, - Runtime, + plugin::{Builder, PluginHandle, TauriPlugin}, + Manager, Runtime, }; -const PLUGIN_NAME: &str = "sample"; #[cfg(target_os = "android")] const PLUGIN_IDENTIFIER: &str = "com.plugin.sample"; @@ -12,13 +11,20 @@ extern "C" { fn init_plugin_sample(webview: tauri::cocoa::base::id); } +pub struct SamplePlugin(PluginHandle); + pub fn init() -> TauriPlugin { - Builder::new(PLUGIN_NAME) - .setup(|app| { - #[cfg(target_os = "android")] - app.initialize_android_plugin(PLUGIN_NAME, PLUGIN_IDENTIFIER, "ExamplePlugin")?; - #[cfg(target_os = "ios")] - app.initialize_ios_plugin(init_plugin_sample)?; + Builder::new("sample") + .setup(|app, api| { + #[cfg(any(target_os = "android", target_os = "ios"))] + { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_sample)?; + app.manage(SamplePlugin(handle)); + } + Ok(()) }) .build() diff --git a/tooling/cli/src/plugin/android.rs b/tooling/cli/src/plugin/android.rs index da1f31466..41fc8c3a7 100644 --- a/tooling/cli/src/plugin/android.rs +++ b/tooling/cli/src/plugin/android.rs @@ -106,9 +106,9 @@ tauri-build = "{}" r#" pub fn init() -> TauriPlugin {{ Builder::new("{name}") - .setup(|app| {{ + .setup(|app, api| {{ #[cfg(target_os = "android")] - app.initialize_android_plugin("{name}", "{identifier}, "ExamplePlugin")?; + let handle = api.register_android_plugin("{identifier}, "ExamplePlugin")?; Ok(()) }}) .build() diff --git a/tooling/cli/src/plugin/init.rs b/tooling/cli/src/plugin/init.rs index 0b9b21fc3..3c38f8193 100644 --- a/tooling/cli/src/plugin/init.rs +++ b/tooling/cli/src/plugin/init.rs @@ -11,7 +11,7 @@ use anyhow::Context; use clap::Parser; use dialoguer::Input; use handlebars::{to_json, Handlebars}; -use heck::{AsKebabCase, ToKebabCase, ToSnakeCase}; +use heck::{AsKebabCase, ToKebabCase, ToPascalCase, ToSnakeCase}; use include_dir::{include_dir, Dir}; use log::warn; use std::{ @@ -193,6 +193,10 @@ pub fn plugin_name_data(data: &mut BTreeMap<&'static str, serde_json::Value>, pl "plugin_name_snake_case", to_json(plugin_name.to_snake_case()), ); + data.insert( + "plugin_name_pascal_case", + to_json(plugin_name.to_pascal_case()), + ); } pub fn crates_metadata() -> Result { diff --git a/tooling/cli/templates/plugin/src/lib.rs b/tooling/cli/templates/plugin/src/lib.rs index 65b1976e9..43b7d736a 100644 --- a/tooling/cli/templates/plugin/src/lib.rs +++ b/tooling/cli/templates/plugin/src/lib.rs @@ -5,7 +5,7 @@ use serde::{ser::Serializer, Serialize}; use tauri::{ command, - plugin::{Builder, TauriPlugin}, + plugin::{Builder, PluginHandle, TauriPlugin}, AppHandle, Manager, Runtime, State, Window, }; @@ -13,7 +13,6 @@ use std::{collections::HashMap, sync::Mutex}; type Result = std::result::Result; -const PLUGIN_NAME: &str = "{{ plugin_name }}"; #[cfg(target_os = "android")] const PLUGIN_IDENTIFIER: &str = "{{ android_package_id }}"; @@ -40,6 +39,8 @@ impl Serialize for Error { #[derive(Default)] struct MyState(Mutex>); +pub struct {{ plugin_name_pascal_case }}Plugin(PluginHandle); + #[command] async fn execute( _app: AppHandle, @@ -52,14 +53,18 @@ async fn execute( /// Initializes the plugin. pub fn init() -> TauriPlugin { - Builder::new(PLUGIN_NAME) + Builder::new("{{ plugin_name }}") .invoke_handler(tauri::generate_handler![execute]) - .setup(|app| { + .setup(|app, api| { // initialize mobile plugins - #[cfg(target_os = "android")] - app.initialize_android_plugin(PLUGIN_NAME, PLUGIN_IDENTIFIER, "ExamplePlugin")?; - #[cfg(target_os = "ios")] - app.initialize_ios_plugin(init_plugin_{{ plugin_name }})?; + #[cfg(any(target_os = "android", target_os = "ios"))] + { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_{{ plugin_name }})?; + app.manage({{ plugin_name_pascal_case }}Plugin(handle)); + } // manage state so it is accessible by the commands app.manage(MyState::default());