Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/tauri-utils/src/config/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use crate::config::Config;
use crate::platform::Target;
use json_patch::merge;
pub use json_patch::merge;
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::ffi::OsStr;
Expand Down
5 changes: 5 additions & 0 deletions crates/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,8 @@ path = "../../examples/streaming/main.rs"
name = "isolation"
path = "../../examples/isolation/main.rs"
required-features = ["isolation"]

[[example]]
name = "precompiled"
path = "../../examples/precompiled/main.rs"
required-features = ["config-toml"]
2 changes: 1 addition & 1 deletion crates/tauri/src/ipc/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl RuntimeAuthority {
}

#[cfg(feature = "dynamic-acl")]
fn add_capability_inner(&mut self, capability: CapabilityFile) -> crate::Result<()> {
pub(crate) fn add_capability_inner(&mut self, capability: CapabilityFile) -> crate::Result<()> {
let mut capabilities = BTreeMap::new();
match capability {
CapabilityFile::Capability(c) => {
Expand Down
10 changes: 10 additions & 0 deletions crates/tauri/src/ipc/capability_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,22 @@ pub trait RuntimeCapability {
fn build(self) -> CapabilityFile;
}

/// we should remove this implementation, use `<CapabilityFile as FromStr>::from_str` instead
impl<T: AsRef<str>> RuntimeCapability for T {
fn build(self) -> CapabilityFile {
self.as_ref().parse().expect("invalid capability")
}
}

// // TODO: Because we have already `impl<T: AsRef<str>> RuntimeCapability for T`,
// // we cant implement `RuntimeCapability` for `CapabilityFile` anymore.
//
// impl RuntimeCapability for CapabilityFile {
// fn build(self) -> CapabilityFile {
// self
// }
// }

/// A builder for a [`Capability`].
pub struct CapabilityBuilder(Capability);

Expand Down
152 changes: 149 additions & 3 deletions crates/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,13 @@ pub use tauri_runtime_wry::{tao, wry};
/// A task to run on the main thread.
pub type SyncTask = Box<dyn FnOnce() + Send>;

use anyhow::Context as _;
use serde::Serialize;
use std::{
borrow::Cow,
collections::HashMap,
fmt::{self, Debug},
fmt::{self, Debug, Display},
path::PathBuf,
sync::MutexGuard,
};
use utils::assets::{AssetKey, CspHash, EmbeddedAssets};
Expand Down Expand Up @@ -354,10 +356,19 @@ pub trait Assets<R: Runtime>: Send + Sync + 'static {
fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;

/// Iterator for the assets.
fn iter(&self) -> Box<tauri_utils::assets::AssetsIter<'_>>;
///
/// This is for optimization purposes only;
/// Implementers may return an empty iterator even if [Assets::get] returns [Some].
fn iter(&self) -> Box<tauri_utils::assets::AssetsIter<'_>> {
Box::new(std::iter::empty())
}

/// Gets the hashes for the CSP tag of the HTML on the given path.
fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
///
/// Implementers may return an empty iterator.
fn csp_hashes(&self, _html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
Box::new(std::iter::empty())
}
}

impl<R: Runtime> Assets<R> for EmbeddedAssets {
Expand All @@ -374,6 +385,42 @@ impl<R: Runtime> Assets<R> for EmbeddedAssets {
}
}

/// A simple `Assets` implementation that reads files from disk directory.
struct DirectoryAssets(PathBuf);

impl DirectoryAssets {
fn new(dir: PathBuf) -> Self {
Self(dir)
}
}

// TODO: [tauri_codegen::embedded_assets::AssetOptions]
impl<R: Runtime> Assets<R> for DirectoryAssets {
fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
fn unwrap_and_log<T, E: Display>(
result: std::result::Result<T, E>,
key: &AssetKey,
) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(e) => {
log::info!("Failed to get asset from path '{}': {}", key.as_ref(), e);
None
}
}
}

// We need to skip the first character (i.e., `/`) of the key.
let path = self.0.join(&key.as_ref()[1..]);
// prevents path traversal.
let safe_path = unwrap_and_log(path::SafePathBuf::new(path), key)?;
// TODO: LRU cache
let asset = unwrap_and_log(std::fs::read(&safe_path), key)?;

Some(Cow::Owned(asset))
}
}

/// User supplied data required inside of a Tauri application.
///
/// # Stability
Expand Down Expand Up @@ -524,6 +571,105 @@ impl<R: Runtime> Context<R> {
}
}

// ref: <https://github.com/tauri-apps/tauri/blob/6a39f49991e613e8f3befe0e8dff288482ccdd89/crates/tauri-codegen/src/context.rs#L134-L477>
#[cfg(all(desktop, feature = "dynamic-acl"))]
pub fn load_runtime_context(
// NOTE: if error, this `Context` has already been modified (tainted), so we do not allow it to be reused
mut self,
src_tauri_dir: &std::path::Path,
tauri_config: Option<Config>,
) -> Result<Self> {
if !src_tauri_dir.is_absolute() {
return Err(
anyhow::anyhow!(
"The `src_tauri_dir` path must be absolute, got: {}",
src_tauri_dir.display()
)
.into(),
);
}

let dev = is_dev();
let target = utils::platform::Target::current();

// Load config from file dynamically.
// ref: <https://github.com/tauri-apps/tauri/blob/6a39f49991e613e8f3befe0e8dff288482ccdd89/crates/tauri-codegen/src/lib.rs#L57-L99>
let mut src_config = utils::config::parse::read_from(target, src_tauri_dir)
.with_context(|| "Failed to read tauri config")?
.0;
if let Some(tauri_config) = tauri_config {
utils::config::parse::merge(&mut src_config, &serde_json::to_value(tauri_config)?);
}
let mut new_config = serde_json::to_value(self.config)?;
utils::config::parse::merge(&mut new_config, &src_config);
// NOTE: unlike [tauri_codegen::get_config], we don't change CWD here,
// because it will change the runtime-behavior of user's application.
self.config = serde_json::from_value(new_config)?;

// Patch `package_info` from `config`.
// ref: <https://github.com/tauri-apps/tauri/blob/6a39f49991e613e8f3befe0e8dff288482ccdd89/crates/tauri-codegen/src/context.rs#L268-L287>
if let Some(product_name) = &self.config.product_name {
self.package_info.name = product_name.clone();
}
if let Some(version) = &self.config.version {
self.package_info.version = version.parse().with_context(|| "Failed to parse version")?;
}
// TODO: PackageInfo::{authors, description, crate_name}

// Supply custom Assets from disk dynamically.
// ref: <https://github.com/tauri-apps/tauri/blob/6a39f49991e613e8f3befe0e8dff288482ccdd89/crates/tauri-codegen/src/context.rs#L176-L207>
let mut new_asset: Option<Box<dyn Assets<R>>> = None;
if let Some(frontend_dist) = &self.config.build.frontend_dist {
match frontend_dist {
utils::config::FrontendDist::Url(_) => {
// do nothing, we don't need supply custom Assets for URL frontend_dist,
// because tauri will fetch the frontend from the URL.
}
utils::config::FrontendDist::Directory(dir) => {
new_asset = Some(Box::new(DirectoryAssets::new(src_tauri_dir.join(dir))));
}
other => {
return Err(anyhow::anyhow!("Unsupported frontend_dist: {:?}", other).into());
}
}
}
if let Some(assets) = new_asset {
self.assets = assets;
}

// Load capabilities from disk dynamically.
// ref: <https://github.com/tauri-apps/tauri/blob/6a39f49991e613e8f3befe0e8dff288482ccdd89/crates/tauri-build/src/acl.rs#L424-L429>
let capabilities_pattern_path = src_tauri_dir.join("./capabilities/**/*");
let capabilities_pattern = capabilities_pattern_path.to_str().with_context(|| {
format!(
"Failed to convert capabilities pattern path to str: {}",
capabilities_pattern_path.display()
)
})?;
let capabilities_from_files = utils::acl::build::parse_capabilities(capabilities_pattern)
.with_context(|| {
format!(
"Failed to parse capabilities from pattern: {}",
capabilities_pattern
)
})?;

// Patch `capabilities` from `config`.
// ref: <https://github.com/tauri-apps/tauri/blob/6a39f49991e613e8f3befe0e8dff288482ccdd89/crates/tauri-codegen/src/context.rs#L403-L408>
// <https://tauri.app/security/capabilities/>
let capabilities = utils::acl::get_capabilities(&self.config, capabilities_from_files, None)?;

// Add capabilities to `ctx`.
// TODO: maybe we should clear the existing capabilities first?
self
.runtime_authority
.add_capability_inner(utils::acl::capability::CapabilityFile::List(
capabilities.into_values().collect(),
))?;

Ok(self)
}

#[cfg(dev)]
#[doc(hidden)]
pub fn with_config_parent(&mut self, config_parent: impl AsRef<std::path::Path>) {
Expand Down
3 changes: 3 additions & 0 deletions examples/precompiled/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Precompiled B--features isolation`

To execute run the following on the root directory of the repository: `cargo run --example precompiled --features="config-toml"`.
21 changes: 21 additions & 0 deletions examples/precompiled/Tauri.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# TODO, FIXME: The following is the basic necessary configuration,
# otherwise `tauri-build` will report an error.

"$schema" = "https://schema.tauri.app/config/2"
productName = "Precompiled Tauri"
identifier = "com.precompiled-tauri.dev"

[app]
# NOTE: IMPORTANT! We must enable `withGlobalTauri` when compile-time,
# because it can't be enabled dynamically at runtime,
# see: <https://github.com/tauri-apps/tauri/blob/339a075e33292dab67766d56a8b988e46640f490/crates/tauri-codegen/src/context.rs#L289-L299>
withGlobalTauri = true

[bundle]
icon = [
"../.icons/32x32.png",
"../.icons/128x128.png",
"../.icons/[email protected]",
"../.icons/icon.icns",
"../.icons/icon.ico"
]
26 changes: 26 additions & 0 deletions examples/precompiled/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use std::env::current_dir;

#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello {name}, You have been greeted from Rust!")
}

fn main() {
let current_dir = current_dir().expect("failed to get current directory");

let context = tauri::generate_context!("../../examples/precompiled/Tauri.toml");
let context = context
.load_runtime_context(&current_dir.join("examples/precompiled/src-tauri"), None)
.expect("failed to load runtime context");

tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(context)
.expect("error while running tauri application");
}
34 changes: 34 additions & 0 deletions examples/precompiled/src-tauri/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Tauri!</title>
</head>
<body>
<h1>Welcome to Tauri!</h1>

<form id="form">
<input id="name" placeholder="Enter a name..." />
<button>Greet</button>
</form>

<p id="message"></p>

<script>
const { invoke } = window.__TAURI__.core

const form = document.querySelector('#form')
const nameEl = document.querySelector('#name')
const messageEl = document.querySelector('#message')

form.addEventListener('submit', async (e) => {
e.preventDefault()

const name = nameEl.value
const newMessage = await invoke('greet', { name })
messageEl.textContent = newMessage
})
</script>
</body>
</html>
30 changes: 30 additions & 0 deletions examples/precompiled/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "../../../crates/tauri-schema-generator/schemas/config.schema.json",
"productName": "Precompiled Tauri Example",
"version": "0.1.0",
"identifier": "com.precompiled-tauri-example.dev",
"build": {
"frontendDist": "frontend"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "Welcome to Precompiled Tauri!",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
]
},
"bundle": {
"icon": [
"../../.icons/32x32.png",
"../../.icons/128x128.png",
"../../.icons/[email protected]",
"../../.icons/icon.icns",
"../../.icons/icon.ico"
]
}
}
Loading