mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
refactor(plugin): add PluginApi and PluginHandle, expose on setup hook (#6291)
This commit is contained in:
committed by
GitHub
parent
ec007ef0d0
commit
6aaba83476
5
.changes/plugin-setup-refactor.md
Normal file
5
.changes/plugin-setup-refactor.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": major
|
||||
---
|
||||
|
||||
Changed the plugin setup hook to take a second argument of type `PluginApi`.
|
||||
@@ -383,90 +383,6 @@ impl<R: Runtime> AppHandle<R> {
|
||||
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::<R>(
|
||||
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<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
plugin: impl AsRef<str>,
|
||||
method: impl AsRef<str>,
|
||||
payload: impl serde::Serialize
|
||||
) -> crate::Result<Result<T, E>> {
|
||||
#[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<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
plugin: impl AsRef<str>,
|
||||
method: impl AsRef<str>,
|
||||
payload: impl serde::Serialize
|
||||
) -> Result<T, E> {
|
||||
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<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
plugin: impl AsRef<str>,
|
||||
method: impl AsRef<str>,
|
||||
payload: impl serde::Serialize
|
||||
) -> Result<Result<T, E>, jni::errors::Error> {
|
||||
use jni::{
|
||||
errors::Error as JniError,
|
||||
objects::JObject,
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
fn run<R: Runtime>(
|
||||
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::<R>(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::<R>(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<R: Runtime> Builder<R> {
|
||||
/// }
|
||||
/// pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// PluginBuilder::new("window")
|
||||
/// .setup(|app| {
|
||||
/// .setup(|app, api| {
|
||||
/// // initialize the plugin here
|
||||
/// Ok(())
|
||||
/// })
|
||||
@@ -2094,57 +1860,6 @@ impl Default for Builder<crate::Wry> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(mobile)]
|
||||
type PendingPluginCallHandler =
|
||||
Box<dyn FnOnce(std::result::Result<serde_json::Value, serde_json::Value>) + Send + 'static>;
|
||||
|
||||
#[cfg(mobile)]
|
||||
static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell<
|
||||
std::sync::Mutex<HashMap<i32, PendingPluginCallHandler>>,
|
||||
> = 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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
pub type Result<T> = StdResult<T, Box<dyn std::error::Error>>;
|
||||
|
||||
/// The plugin interface.
|
||||
pub trait Plugin<R: Runtime>: Send {
|
||||
@@ -59,13 +64,344 @@ pub trait Plugin<R: Runtime>: Send {
|
||||
}
|
||||
}
|
||||
|
||||
type SetupHook<R> = dyn FnOnce(&AppHandle<R>) -> Result<()> + Send;
|
||||
type SetupWithConfigHook<R, T> = dyn FnOnce(&AppHandle<R>, T) -> Result<()> + Send;
|
||||
type SetupHook<R, C> = dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<()> + Send;
|
||||
type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
|
||||
type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
|
||||
type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send;
|
||||
type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
|
||||
|
||||
#[cfg(mobile)]
|
||||
type PendingPluginCallHandler =
|
||||
Box<dyn FnOnce(StdResult<serde_json::Value, serde_json::Value>) + Send + 'static>;
|
||||
|
||||
#[cfg(mobile)]
|
||||
static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell<
|
||||
std::sync::Mutex<HashMap<i32, PendingPluginCallHandler>>,
|
||||
> = 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<R: Runtime> {
|
||||
name: &'static str,
|
||||
handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> PluginHandle<R> {
|
||||
/// Executes the given mobile method.
|
||||
#[cfg(mobile)]
|
||||
pub fn run_mobile_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
method: impl AsRef<str>,
|
||||
payload: impl serde::Serialize,
|
||||
) -> crate::Result<StdResult<T, E>> {
|
||||
#[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<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
method: impl AsRef<str>,
|
||||
payload: impl serde::Serialize,
|
||||
) -> StdResult<T, E> {
|
||||
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<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
method: impl AsRef<str>,
|
||||
payload: impl serde::Serialize,
|
||||
) -> StdResult<StdResult<T, E>, jni::errors::Error> {
|
||||
use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
|
||||
|
||||
fn run<R: Runtime>(
|
||||
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::<R>(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::<R>(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<R: Runtime, C: DeserializeOwned> {
|
||||
handle: AppHandle<R>,
|
||||
name: &'static str,
|
||||
config: C,
|
||||
}
|
||||
|
||||
impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
|
||||
/// 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<PluginHandle<R>> {
|
||||
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<PluginHandle<R>> {
|
||||
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::<R>(
|
||||
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<R> = dyn FnOnce(AppHandle<R>) + Send;
|
||||
///
|
||||
/// pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
|
||||
/// 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<R> = dyn FnOnce(AppHandle<R>) + Send;
|
||||
pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
|
||||
name: &'static str,
|
||||
invoke_handler: Box<InvokeHandler<R>>,
|
||||
setup: Option<Box<SetupHook<R>>>,
|
||||
setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
|
||||
setup: Option<Box<SetupHook<R, C>>>,
|
||||
js_init_script: Option<String>,
|
||||
on_page_load: Box<OnPageLoad<R>>,
|
||||
on_webview_ready: Box<OnWebviewReady<R>>,
|
||||
@@ -156,7 +491,6 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
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<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
|
||||
/// 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<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
///
|
||||
/// fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// 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<F>(mut self, setup: F) -> Self
|
||||
where
|
||||
F: FnOnce(&AppHandle<R>) -> Result<()> + Send + 'static,
|
||||
F: FnOnce(&AppHandle<R>, PluginApi<R, C>) -> 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<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R, Config> {
|
||||
/// tauri::plugin::Builder::<R, Config>::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<F>(mut self, setup_with_config: F) -> Self
|
||||
where
|
||||
F: FnOnce(&AppHandle<R>, 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<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
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<R: Runtime, C: DeserializeOwned = ()> {
|
||||
name: &'static str,
|
||||
app: Option<AppHandle<R>>,
|
||||
invoke_handler: Box<InvokeHandler<R>>,
|
||||
setup: Option<Box<SetupHook<R>>>,
|
||||
setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
|
||||
setup: Option<Box<SetupHook<R, C>>>,
|
||||
js_init_script: Option<String>,
|
||||
on_page_load: Box<OnPageLoad<R>>,
|
||||
on_webview_ready: Box<OnWebviewReady<R>>,
|
||||
@@ -460,10 +750,14 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
|
||||
fn initialize(&mut self, app: &AppHandle<R>, 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(())
|
||||
}
|
||||
|
||||
@@ -95,16 +95,6 @@ impl AppBuilder {
|
||||
#[cfg(debug_assertions)]
|
||||
window.open_devtools();
|
||||
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
let response = app.run_mobile_plugin::<serde_json::Value, serde_json::Value>(
|
||||
"sample",
|
||||
"ping",
|
||||
serde_json::Value::default(),
|
||||
);
|
||||
println!("got response: {:?}", response);
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
std::thread::spawn(|| {
|
||||
let server = match tiny_http::Server::http("localhost:3003") {
|
||||
|
||||
@@ -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<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
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()
|
||||
|
||||
@@ -106,9 +106,9 @@ tauri-build = "{}"
|
||||
r#"
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {{
|
||||
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()
|
||||
|
||||
@@ -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<VersionMetadata> {
|
||||
|
||||
@@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
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<HashMap<String, String>>);
|
||||
|
||||
pub struct {{ plugin_name_pascal_case }}Plugin<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
#[command]
|
||||
async fn execute<R: Runtime>(
|
||||
_app: AppHandle<R>,
|
||||
@@ -52,14 +53,18 @@ async fn execute<R: Runtime>(
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
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());
|
||||
|
||||
Reference in New Issue
Block a user