diff --git a/.changes/global-api-script-path-plugins.md b/.changes/global-api-script-path-plugins.md new file mode 100644 index 000000000..2de6c03a0 --- /dev/null +++ b/.changes/global-api-script-path-plugins.md @@ -0,0 +1,8 @@ +--- +"tauri": patch:feat +"tauri-codegen": patch:feat +"tauri-build": patch:feat +"tauri-plugin": patch:feat +--- + +Allow plugins to define (at compile time) JavaScript that are initialized when `withGlobalTauri` is true. diff --git a/.changes/plugin-global-api-script.md b/.changes/plugin-global-api-script.md new file mode 100644 index 000000000..299385d6e --- /dev/null +++ b/.changes/plugin-global-api-script.md @@ -0,0 +1,5 @@ +--- +"tauri-plugin": patch:feat +--- + +Added `Builder::global_api_script_path` to define a JavaScript file containing the initialization script for the plugin API bindings when `withGlobalTauri` is used. diff --git a/.changes/tauri-utils-plugin-module.md b/.changes/tauri-utils-plugin-module.md new file mode 100644 index 000000000..033ed9f23 --- /dev/null +++ b/.changes/tauri-utils-plugin-module.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": patch:feat +--- + +Added the `plugin` module. diff --git a/core/tauri-build/src/lib.rs b/core/tauri-build/src/lib.rs index 4061d61d7..2c90b5b64 100644 --- a/core/tauri-build/src/lib.rs +++ b/core/tauri-build/src/lib.rs @@ -524,6 +524,8 @@ pub fn try_build(attributes: Attributes) -> Result<()> { acl::save_acl_manifests(&acl_manifests)?; + tauri_utils::plugin::load_global_api_scripts(&out_dir); + println!("cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}"); // TODO: far from ideal, but there's no other way to get the target dir, see diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 0e6c73d13..32c5d23ca 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -22,6 +22,7 @@ use tauri_utils::html::{ inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef, }; use tauri_utils::platform::Target; +use tauri_utils::plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH; use tauri_utils::tokens::{map_lit, str_lit}; use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError}; @@ -456,6 +457,36 @@ pub fn context_codegen(data: ContextData) -> Result>(&file_list_str) + .expect("failed to parse plugin global API script paths"); + + let mut plugins = Vec::new(); + for path in file_list { + plugins.push(std::fs::read_to_string(&path).unwrap_or_else(|e| { + panic!( + "failed to read plugin global API script {}: {e}", + path.display() + ) + })); + } + + Some(plugins) + } else { + None + }; + + let plugin_global_api_script = if let Some(scripts) = plugin_global_api_script { + let scripts = scripts.into_iter().map(|s| quote!(#s)); + quote!(::std::option::Option::Some(&[#(#scripts),*])) + } else { + quote!(::std::option::Option::None) + }; + Ok(quote!({ #[allow(unused_mut, clippy::let_and_return)] let mut context = #root::Context::new( @@ -466,7 +497,8 @@ pub fn context_codegen(data: ContextData) -> Result(name: &str) -> Option { pub struct Builder<'a> { commands: &'a [&'static str], global_scope_schema: Option, + global_api_script_path: Option, android_path: Option, ios_path: Option, } @@ -40,6 +41,7 @@ impl<'a> Builder<'a> { Self { commands, global_scope_schema: None, + global_api_script_path: None, android_path: None, ios_path: None, } @@ -51,6 +53,14 @@ impl<'a> Builder<'a> { self } + /// Sets the path to the script that is injected in the webview when the `withGlobalTauri` configuration is set to true. + /// + /// This is usually an IIFE that injects the plugin API JavaScript bindings to `window.__TAURI__`. + pub fn global_api_script_path>(mut self, path: P) -> Self { + self.global_api_script_path.replace(path.into()); + self + } + /// Sets the Android project path. pub fn android_path>(mut self, android_path: P) -> Self { self.android_path.replace(android_path.into()); @@ -118,6 +128,10 @@ impl<'a> Builder<'a> { acl::build::define_global_scope_schema(global_scope_schema, &name, &out_dir)?; } + if let Some(path) = self.global_api_script_path { + tauri_utils::plugin::define_global_api_script_path(path); + } + mobile::setup(self.android_path, self.ios_path)?; Ok(()) diff --git a/core/tauri-utils/src/lib.rs b/core/tauri-utils/src/lib.rs index 513b51ba2..0cf8c6287 100644 --- a/core/tauri-utils/src/lib.rs +++ b/core/tauri-utils/src/lib.rs @@ -29,6 +29,7 @@ pub mod html; pub mod io; pub mod mime_type; pub mod platform; +pub mod plugin; /// Prepare application resources and sidecars. #[cfg(feature = "resources")] pub mod resources; diff --git a/core/tauri-utils/src/plugin.rs b/core/tauri-utils/src/plugin.rs new file mode 100644 index 000000000..4255d0aa2 --- /dev/null +++ b/core/tauri-utils/src/plugin.rs @@ -0,0 +1,51 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Compile-time and runtime types for Tauri plugins. +#[cfg(feature = "build")] +pub use build::*; + +#[cfg(feature = "build")] +mod build { + use std::{ + env::vars_os, + path::{Path, PathBuf}, + }; + + const GLOBAL_API_SCRIPT_PATH_KEY: &str = "GLOBAL_API_SCRIPT_PATH"; + /// Known file name of the file that contains an array with the path of all API scripts defined with [`define_global_api_script_path`]. + pub const GLOBAL_API_SCRIPT_FILE_LIST_PATH: &str = "__global-api-script.js"; + + /// Defines the path to the global API script using Cargo instructions. + pub fn define_global_api_script_path(path: PathBuf) { + println!( + "cargo:{GLOBAL_API_SCRIPT_PATH_KEY}={}", + path + .canonicalize() + .expect("failed to canonicalize global API script path") + .display() + ) + } + + /// Collects the path of all the global API scripts defined with [`define_global_api_script_path`] + /// and saves them to the out dir with filename [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`]. + pub fn load_global_api_scripts(out_dir: &Path) { + let mut scripts = Vec::new(); + + for (key, value) in vars_os() { + let key = key.to_string_lossy(); + + if key.starts_with("DEP_") && key.ends_with(GLOBAL_API_SCRIPT_PATH_KEY) { + let script_path = PathBuf::from(value); + scripts.push(script_path); + } + } + + std::fs::write( + out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH), + serde_json::to_string(&scripts).expect("failed to serialize global API script paths"), + ) + .expect("failed to write global API script"); + } +} diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 6125c30e0..cfc077c2d 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -388,6 +388,7 @@ pub struct Context { pub(crate) _info_plist: (), pub(crate) pattern: Pattern, pub(crate) runtime_authority: RuntimeAuthority, + pub(crate) plugin_global_api_scripts: Option<&'static [&'static str]>, } impl fmt::Debug for Context { @@ -397,7 +398,8 @@ impl fmt::Debug for Context { .field("default_window_icon", &self.default_window_icon) .field("app_icon", &self.app_icon) .field("package_info", &self.package_info) - .field("pattern", &self.pattern); + .field("pattern", &self.pattern) + .field("plugin_global_api_scripts", &self.plugin_global_api_scripts); #[cfg(all(desktop, feature = "tray-icon"))] d.field("tray_icon", &self.tray_icon); @@ -500,6 +502,7 @@ impl Context { info_plist: (), pattern: Pattern, runtime_authority: RuntimeAuthority, + plugin_global_api_scripts: Option<&'static [&'static str]>, ) -> Self { Self { config, @@ -512,6 +515,7 @@ impl Context { _info_plist: info_plist, pattern, runtime_authority, + plugin_global_api_scripts, } } diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index 13ec77b9c..7a742fa74 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -188,6 +188,9 @@ pub struct AppManager { /// Application pattern. pub pattern: Arc, + /// Global API scripts collected from plugins. + pub plugin_global_api_scripts: Arc>, + /// Application Resources Table pub(crate) resources_table: Arc>, } @@ -274,6 +277,7 @@ impl AppManager { app_icon: context.app_icon, package_info: context.package_info, pattern: Arc::new(context.pattern), + plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts), resources_table: Arc::default(), } } diff --git a/core/tauri/src/manager/webview.rs b/core/tauri/src/manager/webview.rs index 6547d6ffd..40cdaeb06 100644 --- a/core/tauri/src/manager/webview.rs +++ b/core/tauri/src/manager/webview.rs @@ -208,6 +208,12 @@ impl WebviewManager { ); } + if let Some(plugin_global_api_scripts) = &*app_manager.plugin_global_api_scripts { + for script in plugin_global_api_scripts.iter() { + webview_attributes = webview_attributes.initialization_script(script); + } + } + pending.webview_attributes = webview_attributes; let mut registered_scheme_protocols = Vec::new(); diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index ce7bbad9c..156d24576 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -127,6 +127,7 @@ pub fn mock_context>(assets: A) -> crate::Context { _info_plist: (), pattern: Pattern::Brownfield, runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()), + plugin_global_api_scripts: None, } } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index c38fa97f4..aa51749a3 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3152,7 +3152,7 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tauri" -version = "2.0.0-beta.10" +version = "2.0.0-beta.11" dependencies = [ "anyhow", "bytes", @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "anyhow", "cargo_toml", @@ -3223,7 +3223,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "base64 0.22.0", "brotli", @@ -3248,7 +3248,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "heck", "proc-macro2", @@ -3260,7 +3260,7 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "anyhow", "glob", @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "gtk", "http", @@ -3302,7 +3302,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "cocoa", "gtk", @@ -3324,7 +3324,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-beta.8" +version = "2.0.0-beta.9" dependencies = [ "aes-gcm", "brotli", diff --git a/examples/api/src-tauri/tauri-plugin-sample/api-iife.js b/examples/api/src-tauri/tauri-plugin-sample/api-iife.js new file mode 100644 index 000000000..cb6dfc79d --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/api-iife.js @@ -0,0 +1,7 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +if ('__TAURI__' in window) { + window.__TAURI__.sample = {} +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/build.rs b/examples/api/src-tauri/tauri-plugin-sample/build.rs index 09bd37d03..9ab61beb3 100644 --- a/examples/api/src-tauri/tauri-plugin-sample/build.rs +++ b/examples/api/src-tauri/tauri-plugin-sample/build.rs @@ -8,5 +8,6 @@ fn main() { tauri_plugin::Builder::new(COMMANDS) .android_path("android") .ios_path("ios") + .global_api_script_path("./api-iife.js") .build(); }