Skip to content

Commit

Permalink
feat(core): add asset_resolver API (#2879)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Nov 13, 2021
1 parent d13c48f commit 7c6c7ad
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 59 deletions.
5 changes: 5 additions & 0 deletions .changes/asset-resolver.md
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Expose the `asset_resolver` API on the `App` and `AppHandle` structs.
22 changes: 21 additions & 1 deletion core/tauri/src/app.rs
Expand Up @@ -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},
Expand Down Expand Up @@ -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<R: Runtime> {
manager: WindowManager<R>,
}

impl<R: Runtime> AssetResolver<R> {
/// Gets the app asset associated with the given path.
pub fn get(&self, path: String) -> Option<Asset> {
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.
Expand Down Expand Up @@ -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<R> {
AssetResolver {
manager: self.manager.clone(),
}
}
}
};
}
Expand Down
5 changes: 4 additions & 1 deletion core/tauri/src/lib.rs
Expand Up @@ -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::{
Expand Down
127 changes: 70 additions & 57 deletions core/tauri/src/manager.rs
Expand Up @@ -95,6 +95,14 @@ impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
}
}

/// A resolved asset.
pub struct Asset {
/// The asset bytes.
pub bytes: Vec<u8>,
/// The asset's mime type.
pub mime_type: String,
}

/// Uses a custom URI scheme handler to resolve file requests
pub struct CustomProtocol<R: Runtime> {
/// Handler for protocol
Expand Down Expand Up @@ -371,77 +379,82 @@ impl<R: Runtime> WindowManager<R> {
})
}

pub fn get_asset(&self, mut path: String) -> Result<Asset, Box<dyn std::error::Error>> {
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::<String>()
};
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<dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + 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
.next()
.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::<String>()
};
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)
})
}

Expand Down

0 comments on commit 7c6c7ad

Please sign in to comment.