Эх сурвалжийг харах

feat(core): add `asset_resolver` API (#2879)

Lucas Fernandes Nogueira 3 жил өмнө
parent
commit
7c6c7adcc4

+ 5 - 0
.changes/asset-resolver.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Expose the `asset_resolver` API on the `App` and `AppHandle` structs.

+ 21 - 1
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<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.
@@ -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(),
+        }
+      }
     }
   };
 }

+ 4 - 1
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::{

+ 70 - 57
core/tauri/src/manager.rs

@@ -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
@@ -371,15 +379,71 @@ 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
@@ -387,61 +451,10 @@ impl<R: Runtime> WindowManager<R> {
         .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)
     })
   }