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
This commit is contained in:
Lucas Fernandes Nogueira 2024-03-12 12:01:13 -03:00 committed by GitHub
parent 3f039c18b1
commit e227fe02f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 151 additions and 10 deletions

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,5 @@
---
"tauri-utils": patch:feat
---
Added the `plugin` module.

View File

@ -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 <https://github.com/rust-lang/cargo/issues/5457>

View File

@ -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<TokenStream, EmbeddedAssetsE
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved));
let plugin_global_api_script_file_list_path = out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH);
let plugin_global_api_script =
if config.app.with_global_tauri && plugin_global_api_script_file_list_path.exists() {
let file_list_str = std::fs::read_to_string(plugin_global_api_script_file_list_path)
.expect("failed to read plugin global API script paths");
let file_list = serde_json::from_str::<Vec<PathBuf>>(&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<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#runtime_authority
#runtime_authority,
#plugin_global_api_script
);
#with_tray_icon_code
context

View File

@ -31,6 +31,7 @@ pub fn plugin_config<T: DeserializeOwned>(name: &str) -> Option<T> {
pub struct Builder<'a> {
commands: &'a [&'static str],
global_scope_schema: Option<schemars::schema::RootSchema>,
global_api_script_path: Option<PathBuf>,
android_path: Option<PathBuf>,
ios_path: Option<PathBuf>,
}
@ -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<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.global_api_script_path.replace(path.into());
self
}
/// Sets the Android project path.
pub fn android_path<P: Into<PathBuf>>(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(())

View File

@ -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;

View File

@ -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");
}
}

View File

@ -388,6 +388,7 @@ pub struct Context<R: Runtime> {
pub(crate) _info_plist: (),
pub(crate) pattern: Pattern,
pub(crate) runtime_authority: RuntimeAuthority,
pub(crate) plugin_global_api_scripts: Option<&'static [&'static str]>,
}
impl<R: Runtime> fmt::Debug for Context<R> {
@ -397,7 +398,8 @@ impl<R: Runtime> fmt::Debug for Context<R> {
.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<R: Runtime> Context<R> {
info_plist: (),
pattern: Pattern,
runtime_authority: RuntimeAuthority,
plugin_global_api_scripts: Option<&'static [&'static str]>,
) -> Self {
Self {
config,
@ -512,6 +515,7 @@ impl<R: Runtime> Context<R> {
_info_plist: info_plist,
pattern,
runtime_authority,
plugin_global_api_scripts,
}
}

View File

@ -188,6 +188,9 @@ pub struct AppManager<R: Runtime> {
/// Application pattern.
pub pattern: Arc<Pattern>,
/// Global API scripts collected from plugins.
pub plugin_global_api_scripts: Arc<Option<&'static [&'static str]>>,
/// Application Resources Table
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
}
@ -274,6 +277,7 @@ impl<R: Runtime> AppManager<R> {
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(),
}
}

View File

@ -208,6 +208,12 @@ impl<R: Runtime> WebviewManager<R> {
);
}
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();

View File

@ -127,6 +127,7 @@ pub fn mock_context<R: Runtime, A: Assets<R>>(assets: A) -> crate::Context<R> {
_info_plist: (),
pattern: Pattern::Brownfield,
runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()),
plugin_global_api_scripts: None,
}
}

View File

@ -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",

View File

@ -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 = {}
}

View File

@ -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();
}