From e227fe02f986e145c0731a64693e1c830a9eb5b0 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Tue, 12 Mar 2024 12:01:13 -0300 Subject: [PATCH] feat(core): allow defining global API script on plugin build (#9156) * feat(core): allow defining global API script on plugin build Adds `tauri_plugin::Builder::global_api_script_path` so plugin authors can define the JavaScript global API bindings (supposed to be injected to `window.__TAURI__`) at compile time, so the string is only part of the binary when withGlobalTauri is true. Currently this needs to be done manually at runtime (and it's always added to the binary via include_str). * prefix variable * use list of scripts instead of combining them * static str * header [skip ci] * slice --- .changes/global-api-script-path-plugins.md | 8 +++ .changes/plugin-global-api-script.md | 5 ++ .changes/tauri-utils-plugin-module.md | 5 ++ core/tauri-build/src/lib.rs | 2 + core/tauri-codegen/src/context.rs | 34 ++++++++++++- core/tauri-plugin/src/build/mod.rs | 14 +++++ core/tauri-utils/src/lib.rs | 1 + core/tauri-utils/src/plugin.rs | 51 +++++++++++++++++++ core/tauri/src/lib.rs | 6 ++- core/tauri/src/manager/mod.rs | 4 ++ core/tauri/src/manager/webview.rs | 6 +++ core/tauri/src/test/mod.rs | 1 + examples/api/src-tauri/Cargo.lock | 16 +++--- .../src-tauri/tauri-plugin-sample/api-iife.js | 7 +++ .../src-tauri/tauri-plugin-sample/build.rs | 1 + 15 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 .changes/global-api-script-path-plugins.md create mode 100644 .changes/plugin-global-api-script.md create mode 100644 .changes/tauri-utils-plugin-module.md create mode 100644 core/tauri-utils/src/plugin.rs create mode 100644 examples/api/src-tauri/tauri-plugin-sample/api-iife.js 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(); }