Skip to content

Commit

Permalink
refactor(plugin): add PluginApi and PluginHandle, expose on setup hook (
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Feb 16, 2023
1 parent ec007ef commit 6aaba83
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 377 deletions.
5 changes: 5 additions & 0 deletions .changes/plugin-setup-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": major
---

Changed the plugin setup hook to take a second argument of type `PluginApi`.
287 changes: 1 addition & 286 deletions core/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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())
})
}
}
};
}
Expand Down Expand Up @@ -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(())
/// })
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion core/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 6aaba83

Please sign in to comment.