mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
feat(core): add support to universal app links on macOS (#14031)
* feat(core): add support to universal app links on macOS follow-up for https://github.com/tauri-apps/tao/pull/1108 * fix ci * clippy * ignore empty schemes
This commit is contained in:
committed by
GitHub
parent
20e53a4b95
commit
cc8c0b5317
5
.changes/support-raw-entitlements.md
Normal file
5
.changes/support-raw-entitlements.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-bundler": minor:feat
|
||||
---
|
||||
|
||||
Support providing `plist::Value` as macOS entitlements.
|
||||
7
.changes/universal-app-links-macos.md
Normal file
7
.changes/universal-app-links-macos.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri-cli": minor:feat
|
||||
"@tauri-apps/cli": minor:feat
|
||||
"tauri-utils": minor:feat
|
||||
---
|
||||
|
||||
Added support to universal app links on macOS with the `plugins > deep-link > desktop > domains` configuration.
|
||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -8411,11 +8411,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.34.1"
|
||||
version = "0.34.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b65dc99ae111a3255027d1eca24a3833bb3267d4556a6defddb455f3ca4f5b6c"
|
||||
checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2 0.6.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
|
||||
@@ -71,3 +71,4 @@ opt-level = "s"
|
||||
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
|
||||
tauri = { path = "./crates/tauri" }
|
||||
tauri-plugin = { path = "./crates/tauri-plugin" }
|
||||
tauri-utils = { path = "./crates/tauri-utils" }
|
||||
|
||||
@@ -46,6 +46,7 @@ regex = "1"
|
||||
goblin = "0.9"
|
||||
plist = "1"
|
||||
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
bitness = "0.4"
|
||||
windows-registry = "0.5"
|
||||
|
||||
@@ -45,8 +45,8 @@ pub use self::{
|
||||
category::AppCategory,
|
||||
settings::{
|
||||
AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
|
||||
DmgSettings, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind, Position,
|
||||
RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,
|
||||
DmgSettings, Entitlements, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind,
|
||||
Position, RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,
|
||||
},
|
||||
};
|
||||
pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
|
||||
|
||||
@@ -312,6 +312,7 @@ fn create_info_plist(
|
||||
plist::Value::Array(
|
||||
protocols
|
||||
.iter()
|
||||
.filter(|p| !p.schemes.is_empty())
|
||||
.map(|protocol| {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
dict.insert(
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
use std::{
|
||||
env::{var, var_os},
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::{error::NotarizeAuthError, Settings};
|
||||
use crate::{error::NotarizeAuthError, Entitlements, Settings};
|
||||
|
||||
pub struct SignTarget {
|
||||
pub path: PathBuf,
|
||||
@@ -51,15 +51,20 @@ pub fn sign(
|
||||
log::info!(action = "Signing"; "with identity \"{}\"", keychain.signing_identity());
|
||||
|
||||
for target in targets {
|
||||
let entitlements_path = if target.is_an_executable {
|
||||
settings.macos().entitlements.as_ref().map(Path::new)
|
||||
} else {
|
||||
None
|
||||
let (entitlements_path, _temp_file) = match settings.macos().entitlements.as_ref() {
|
||||
Some(Entitlements::Path(path)) => (Some(path.to_owned()), None),
|
||||
Some(Entitlements::Plist(plist)) => {
|
||||
let mut temp_file = tempfile::NamedTempFile::new()?;
|
||||
plist::to_writer_xml(temp_file.as_file_mut(), &plist)?;
|
||||
(Some(temp_file.path().to_path_buf()), Some(temp_file))
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
keychain
|
||||
.sign(
|
||||
&target.path,
|
||||
entitlements_path,
|
||||
entitlements_path.as_deref(),
|
||||
target.is_an_executable && settings.macos().hardened_runtime,
|
||||
)
|
||||
.map_err(Box::new)?;
|
||||
|
||||
@@ -360,12 +360,21 @@ pub struct MacOsSettings {
|
||||
pub hardened_runtime: bool,
|
||||
/// Provider short name for notarization.
|
||||
pub provider_short_name: Option<String>,
|
||||
/// Path to the entitlements.plist file.
|
||||
pub entitlements: Option<String>,
|
||||
/// Path or contents of the entitlements.plist file.
|
||||
pub entitlements: Option<Entitlements>,
|
||||
/// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist.
|
||||
pub info_plist: Option<PlistKind>,
|
||||
}
|
||||
|
||||
/// Entitlements for macOS code signing.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Entitlements {
|
||||
/// Path to the entitlements.plist file.
|
||||
Path(PathBuf),
|
||||
/// Raw plist::Value.
|
||||
Plist(plist::Value),
|
||||
}
|
||||
|
||||
/// Plist format.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PlistKind {
|
||||
|
||||
@@ -697,7 +697,9 @@ pub fn build_wix_app_installer(
|
||||
.iter()
|
||||
.flat_map(|p| &p.schemes)
|
||||
.collect::<Vec<_>>();
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
if !schemes.is_empty() {
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = custom_template_path {
|
||||
|
||||
@@ -495,7 +495,9 @@ fn build_nsis_app_installer(
|
||||
.iter()
|
||||
.flat_map(|p| &p.schemes)
|
||||
.collect::<Vec<_>>();
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
if !schemes.is_empty() {
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
}
|
||||
|
||||
let silent_webview2_install = if let WebviewInstallMode::DownloadBootstrapper { silent }
|
||||
|
||||
@@ -858,7 +858,7 @@ impl AppSettings for RustAppSettings {
|
||||
let mut settings = tauri_config_to_bundle_settings(
|
||||
self,
|
||||
features,
|
||||
config.identifier.clone(),
|
||||
config,
|
||||
config.bundle.clone(),
|
||||
updater_settings,
|
||||
arch64bits,
|
||||
@@ -1263,7 +1263,7 @@ pub fn get_profile_dir(options: &Options) -> &str {
|
||||
fn tauri_config_to_bundle_settings(
|
||||
settings: &RustAppSettings,
|
||||
features: &[String],
|
||||
identifier: String,
|
||||
tauri_config: &Config,
|
||||
config: crate::helpers::config::BundleConfig,
|
||||
updater_config: Option<UpdaterSettings>,
|
||||
arch64bits: bool,
|
||||
@@ -1386,8 +1386,59 @@ fn tauri_config_to_bundle_settings(
|
||||
BundleResources::Map(map) => (None, Some(map)),
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let entitlements = if let Some(plugin_config) = tauri_config
|
||||
.plugins
|
||||
.0
|
||||
.get("deep-link")
|
||||
.and_then(|c| c.get("desktop").cloned())
|
||||
{
|
||||
let protocols: DesktopDeepLinks =
|
||||
serde_json::from_value(plugin_config).context("failed to parse deep link plugin config")?;
|
||||
let domains = match protocols {
|
||||
DesktopDeepLinks::One(protocol) => protocol.domains,
|
||||
DesktopDeepLinks::List(protocols) => protocols.into_iter().flat_map(|p| p.domains).collect(),
|
||||
};
|
||||
|
||||
if domains.is_empty() {
|
||||
config
|
||||
.macos
|
||||
.entitlements
|
||||
.map(PathBuf::from)
|
||||
.map(tauri_bundler::bundle::Entitlements::Path)
|
||||
} else {
|
||||
let mut app_links_entitlements = plist::Dictionary::new();
|
||||
app_links_entitlements.insert(
|
||||
"com.apple.developer.associated-domains".to_string(),
|
||||
domains
|
||||
.into_iter()
|
||||
.map(|domain| format!("applinks:{domain}").into())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
);
|
||||
let entitlements = if let Some(user_provided_entitlements) = config.macos.entitlements {
|
||||
crate::helpers::plist::merge_plist(vec![
|
||||
PathBuf::from(user_provided_entitlements).into(),
|
||||
plist::Value::Dictionary(app_links_entitlements).into(),
|
||||
])?
|
||||
} else {
|
||||
app_links_entitlements.into()
|
||||
};
|
||||
|
||||
Some(tauri_bundler::bundle::Entitlements::Plist(entitlements))
|
||||
}
|
||||
} else {
|
||||
config
|
||||
.macos
|
||||
.entitlements
|
||||
.map(PathBuf::from)
|
||||
.map(tauri_bundler::bundle::Entitlements::Path)
|
||||
};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let entitlements = None;
|
||||
|
||||
Ok(BundleSettings {
|
||||
identifier: Some(identifier),
|
||||
identifier: Some(tauri_config.identifier.clone()),
|
||||
publisher: config.publisher,
|
||||
homepage: config.homepage,
|
||||
icon: Some(config.icon),
|
||||
@@ -1487,7 +1538,7 @@ fn tauri_config_to_bundle_settings(
|
||||
skip_stapling: false,
|
||||
hardened_runtime: config.macos.hardened_runtime,
|
||||
provider_short_name,
|
||||
entitlements: config.macos.entitlements,
|
||||
entitlements,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
info_plist: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -23,7 +23,7 @@ wry = { version = "0.53.2", default-features = false, features = [
|
||||
"os-webview",
|
||||
"linux-body",
|
||||
] }
|
||||
tao = { version = "0.34.1", default-features = false, features = ["rwh_06"] }
|
||||
tao = { version = "0.34.2", default-features = false, features = ["rwh_06"] }
|
||||
tauri-runtime = { version = "2.8.0", path = "../tauri-runtime" }
|
||||
tauri-utils = { version = "2.7.0", path = "../tauri-utils" }
|
||||
raw-window-handle = "0.6"
|
||||
|
||||
@@ -1198,7 +1198,17 @@ pub struct FileAssociation {
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct DeepLinkProtocol {
|
||||
/// URL schemes to associate with this app without `://`. For example `my-app`
|
||||
#[serde(default)]
|
||||
pub schemes: Vec<String>,
|
||||
/// Domains to associate with this app. For example `example.com`.
|
||||
/// Currently only supported on macOS, translating to an [universal app link].
|
||||
///
|
||||
/// Note that universal app links require signed apps with a provisioning profile to work.
|
||||
/// You can accomplish that by including the `embedded.provisionprofile` file in the `macOS > files` option.
|
||||
///
|
||||
/// [universal app link]: https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app
|
||||
#[serde(default)]
|
||||
pub domains: Vec<String>,
|
||||
/// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
|
||||
pub name: Option<String>,
|
||||
/// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
|
||||
|
||||
Reference in New Issue
Block a user