diff --git a/.changes/asset-resolver.md b/.changes/asset-resolver.md new file mode 100644 index 000000000..cadbb49e7 --- /dev/null +++ b/.changes/asset-resolver.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +Expose the `asset_resolver` API on the `App` and `AppHandle` structs. diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 998958837..a4c97e68a 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -8,7 +8,7 @@ pub(crate) mod tray; use crate::{ command::{CommandArg, CommandItem}, hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook}, - manager::{CustomProtocol, WindowManager}, + manager::{Asset, CustomProtocol, WindowManager}, plugin::{Plugin, PluginStore}, runtime::{ http::{Request as HttpRequest, Response as HttpResponse}, @@ -169,6 +169,19 @@ impl PathResolver { } } +/// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface. +#[derive(Debug, Clone)] +pub struct AssetResolver { + manager: WindowManager, +} + +impl AssetResolver { + /// Gets the app asset associated with the given path. + pub fn get(&self, path: String) -> Option { + self.manager.get_asset(path).ok() + } +} + /// A handle to the currently running application. /// /// This type implements [`Manager`] which allows for manipulation of global application items. @@ -400,6 +413,13 @@ macro_rules! shared_app_impl { pub fn package_info(&self) -> &PackageInfo { self.manager.package_info() } + + /// The application's asset resolver. + pub fn asset_resolver(&self) -> AssetResolver { + AssetResolver { + manager: self.manager.clone(), + } + } } }; } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index a7fbe9e01..3b3fa00ea 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -86,11 +86,14 @@ pub use { self::window::menu::MenuEvent, }; pub use { - self::app::{App, AppHandle, Builder, CloseRequestApi, Event, GlobalWindowEvent, PathResolver}, + self::app::{ + App, AppHandle, AssetResolver, Builder, CloseRequestApi, Event, GlobalWindowEvent, PathResolver, + }, self::hooks::{ Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload, SetupHook, }, + self::manager::Asset, self::runtime::{ webview::{WebviewAttributes, WindowBuilder}, window::{ diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 158c89966..e357a3623 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -95,6 +95,14 @@ impl fmt::Debug for InnerWindowManager { } } +/// A resolved asset. +pub struct Asset { + /// The asset bytes. + pub bytes: Vec, + /// The asset's mime type. + pub mime_type: String, +} + /// Uses a custom URI scheme handler to resolve file requests pub struct CustomProtocol { /// Handler for protocol @@ -371,15 +379,71 @@ impl WindowManager { }) } + pub fn get_asset(&self, mut path: String) -> Result> { + let assets = &self.inner.assets; + if path.ends_with('/') { + path.pop(); + } + path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + let path = if path.is_empty() { + // if the url is `tauri://localhost`, we should load `index.html` + "index.html".to_string() + } else { + // skip leading `/` + path.chars().skip(1).collect::() + }; + let is_javascript = path.ends_with(".js") || path.ends_with(".cjs") || path.ends_with(".mjs"); + let is_html = path.ends_with(".html"); + + let asset_response = assets + .get(&path.as_str().into()) + .or_else(|| assets.get(&format!("{}/index.html", path.as_str()).into())) + .or_else(|| { + #[cfg(debug_assertions)] + eprintln!("Asset `{}` not found; fallback to index.html", path); // TODO log::error! + assets.get(&"index.html".into()) + }) + .ok_or_else(|| crate::Error::AssetNotFound(path.clone())) + .map(Cow::into_owned); + + match asset_response { + Ok(asset) => { + let final_data = match is_javascript || is_html { + true => String::from_utf8_lossy(&asset) + .into_owned() + .replacen( + "__TAURI__INVOKE_KEY_TOKEN__", + &self.generate_invoke_key().to_string(), + 1, + ) + .as_bytes() + .to_vec(), + false => asset, + }; + let mime_type = MimeType::parse(&final_data, &path); + Ok(Asset { + bytes: final_data, + mime_type, + }) + } + Err(e) => { + #[cfg(debug_assertions)] + eprintln!("{:?}", e); // TODO log::error! + Err(Box::new(e)) + } + } + } + #[allow(clippy::type_complexity)] fn prepare_uri_scheme_protocol( &self, ) -> Box Result> + Send + Sync> { - let assets = self.inner.assets.clone(); let manager = self.clone(); Box::new(move |request| { - let mut path = request + let path = request .uri() .split(&['?', '#'][..]) // ignore query string @@ -387,61 +451,10 @@ impl WindowManager { .unwrap() .to_string() .replace("tauri://localhost", ""); - if path.ends_with('/') { - path.pop(); - } - path = percent_encoding::percent_decode(path.as_bytes()) - .decode_utf8_lossy() - .to_string(); - let path = if path.is_empty() { - // if the url is `tauri://localhost`, we should load `index.html` - "index.html".to_string() - } else { - // skip leading `/` - path.chars().skip(1).collect::() - }; - let is_javascript = path.ends_with(".js") || path.ends_with(".cjs") || path.ends_with(".mjs"); - let is_html = path.ends_with(".html"); - - let asset_response = assets - .get(&path.as_str().into()) - .or_else(|| assets.get(&format!("{}/index.html", path.as_str()).into())) - .or_else(|| { - #[cfg(debug_assertions)] - eprintln!("Asset `{}` not found; fallback to index.html", path); // TODO log::error! - assets.get(&"index.html".into()) - }) - .ok_or_else(|| crate::Error::AssetNotFound(path.clone())) - .map(Cow::into_owned); - - match asset_response { - Ok(asset) => { - let final_data = match is_javascript || is_html { - true => String::from_utf8_lossy(&asset) - .into_owned() - .replacen( - "__TAURI__INVOKE_KEY_TOKEN__", - &manager.generate_invoke_key().to_string(), - 1, - ) - .as_bytes() - .to_vec(), - false => asset, - }; - - let mime_type = MimeType::parse(&final_data, &path); - Ok( - HttpResponseBuilder::new() - .mimetype(&mime_type) - .body(final_data)?, - ) - } - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("{:?}", e); // TODO log::error! - Err(Box::new(e)) - } - } + let asset = manager.get_asset(path)?; + HttpResponseBuilder::new() + .mimetype(&asset.mime_type) + .body(asset.bytes) }) }