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]]
|
[[package]]
|
||||||
name = "tao"
|
name = "tao"
|
||||||
version = "0.34.1"
|
version = "0.34.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b65dc99ae111a3255027d1eca24a3833bb3267d4556a6defddb455f3ca4f5b6c"
|
checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.7.0",
|
"bitflags 2.7.0",
|
||||||
|
"block2 0.6.0",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
|||||||
@@ -71,3 +71,4 @@ opt-level = "s"
|
|||||||
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
|
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
|
||||||
tauri = { path = "./crates/tauri" }
|
tauri = { path = "./crates/tauri" }
|
||||||
tauri-plugin = { path = "./crates/tauri-plugin" }
|
tauri-plugin = { path = "./crates/tauri-plugin" }
|
||||||
|
tauri-utils = { path = "./crates/tauri-utils" }
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ regex = "1"
|
|||||||
goblin = "0.9"
|
goblin = "0.9"
|
||||||
plist = "1"
|
plist = "1"
|
||||||
|
|
||||||
|
|
||||||
[target."cfg(target_os = \"windows\")".dependencies]
|
[target."cfg(target_os = \"windows\")".dependencies]
|
||||||
bitness = "0.4"
|
bitness = "0.4"
|
||||||
windows-registry = "0.5"
|
windows-registry = "0.5"
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ pub use self::{
|
|||||||
category::AppCategory,
|
category::AppCategory,
|
||||||
settings::{
|
settings::{
|
||||||
AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
|
AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
|
||||||
DmgSettings, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind, Position,
|
DmgSettings, Entitlements, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind,
|
||||||
RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,
|
Position, RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
|
pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
|
||||||
|
|||||||
@@ -312,6 +312,7 @@ fn create_info_plist(
|
|||||||
plist::Value::Array(
|
plist::Value::Array(
|
||||||
protocols
|
protocols
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|p| !p.schemes.is_empty())
|
||||||
.map(|protocol| {
|
.map(|protocol| {
|
||||||
let mut dict = plist::Dictionary::new();
|
let mut dict = plist::Dictionary::new();
|
||||||
dict.insert(
|
dict.insert(
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
env::{var, var_os},
|
env::{var, var_os},
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
path::{Path, PathBuf},
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{error::NotarizeAuthError, Settings};
|
use crate::{error::NotarizeAuthError, Entitlements, Settings};
|
||||||
|
|
||||||
pub struct SignTarget {
|
pub struct SignTarget {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
@@ -51,15 +51,20 @@ pub fn sign(
|
|||||||
log::info!(action = "Signing"; "with identity \"{}\"", keychain.signing_identity());
|
log::info!(action = "Signing"; "with identity \"{}\"", keychain.signing_identity());
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
let entitlements_path = if target.is_an_executable {
|
let (entitlements_path, _temp_file) = match settings.macos().entitlements.as_ref() {
|
||||||
settings.macos().entitlements.as_ref().map(Path::new)
|
Some(Entitlements::Path(path)) => (Some(path.to_owned()), None),
|
||||||
} else {
|
Some(Entitlements::Plist(plist)) => {
|
||||||
None
|
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
|
keychain
|
||||||
.sign(
|
.sign(
|
||||||
&target.path,
|
&target.path,
|
||||||
entitlements_path,
|
entitlements_path.as_deref(),
|
||||||
target.is_an_executable && settings.macos().hardened_runtime,
|
target.is_an_executable && settings.macos().hardened_runtime,
|
||||||
)
|
)
|
||||||
.map_err(Box::new)?;
|
.map_err(Box::new)?;
|
||||||
|
|||||||
@@ -360,12 +360,21 @@ pub struct MacOsSettings {
|
|||||||
pub hardened_runtime: bool,
|
pub hardened_runtime: bool,
|
||||||
/// Provider short name for notarization.
|
/// Provider short name for notarization.
|
||||||
pub provider_short_name: Option<String>,
|
pub provider_short_name: Option<String>,
|
||||||
/// Path to the entitlements.plist file.
|
/// Path or contents of the entitlements.plist file.
|
||||||
pub entitlements: Option<String>,
|
pub entitlements: Option<Entitlements>,
|
||||||
/// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist.
|
/// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist.
|
||||||
pub info_plist: Option<PlistKind>,
|
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.
|
/// Plist format.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PlistKind {
|
pub enum PlistKind {
|
||||||
|
|||||||
@@ -697,7 +697,9 @@ pub fn build_wix_app_installer(
|
|||||||
.iter()
|
.iter()
|
||||||
.flat_map(|p| &p.schemes)
|
.flat_map(|p| &p.schemes)
|
||||||
.collect::<Vec<_>>();
|
.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 {
|
if let Some(path) = custom_template_path {
|
||||||
|
|||||||
@@ -495,7 +495,9 @@ fn build_nsis_app_installer(
|
|||||||
.iter()
|
.iter()
|
||||||
.flat_map(|p| &p.schemes)
|
.flat_map(|p| &p.schemes)
|
||||||
.collect::<Vec<_>>();
|
.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 }
|
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(
|
let mut settings = tauri_config_to_bundle_settings(
|
||||||
self,
|
self,
|
||||||
features,
|
features,
|
||||||
config.identifier.clone(),
|
config,
|
||||||
config.bundle.clone(),
|
config.bundle.clone(),
|
||||||
updater_settings,
|
updater_settings,
|
||||||
arch64bits,
|
arch64bits,
|
||||||
@@ -1263,7 +1263,7 @@ pub fn get_profile_dir(options: &Options) -> &str {
|
|||||||
fn tauri_config_to_bundle_settings(
|
fn tauri_config_to_bundle_settings(
|
||||||
settings: &RustAppSettings,
|
settings: &RustAppSettings,
|
||||||
features: &[String],
|
features: &[String],
|
||||||
identifier: String,
|
tauri_config: &Config,
|
||||||
config: crate::helpers::config::BundleConfig,
|
config: crate::helpers::config::BundleConfig,
|
||||||
updater_config: Option<UpdaterSettings>,
|
updater_config: Option<UpdaterSettings>,
|
||||||
arch64bits: bool,
|
arch64bits: bool,
|
||||||
@@ -1386,8 +1386,59 @@ fn tauri_config_to_bundle_settings(
|
|||||||
BundleResources::Map(map) => (None, Some(map)),
|
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 {
|
Ok(BundleSettings {
|
||||||
identifier: Some(identifier),
|
identifier: Some(tauri_config.identifier.clone()),
|
||||||
publisher: config.publisher,
|
publisher: config.publisher,
|
||||||
homepage: config.homepage,
|
homepage: config.homepage,
|
||||||
icon: Some(config.icon),
|
icon: Some(config.icon),
|
||||||
@@ -1487,7 +1538,7 @@ fn tauri_config_to_bundle_settings(
|
|||||||
skip_stapling: false,
|
skip_stapling: false,
|
||||||
hardened_runtime: config.macos.hardened_runtime,
|
hardened_runtime: config.macos.hardened_runtime,
|
||||||
provider_short_name,
|
provider_short_name,
|
||||||
entitlements: config.macos.entitlements,
|
entitlements,
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
info_plist: None,
|
info_plist: None,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ wry = { version = "0.53.2", default-features = false, features = [
|
|||||||
"os-webview",
|
"os-webview",
|
||||||
"linux-body",
|
"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-runtime = { version = "2.8.0", path = "../tauri-runtime" }
|
||||||
tauri-utils = { version = "2.7.0", path = "../tauri-utils" }
|
tauri-utils = { version = "2.7.0", path = "../tauri-utils" }
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
|
|||||||
@@ -1198,7 +1198,17 @@ pub struct FileAssociation {
|
|||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
pub struct DeepLinkProtocol {
|
pub struct DeepLinkProtocol {
|
||||||
/// URL schemes to associate with this app without `://`. For example `my-app`
|
/// URL schemes to associate with this app without `://`. For example `my-app`
|
||||||
|
#[serde(default)]
|
||||||
pub schemes: Vec<String>,
|
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]>`
|
/// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
/// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
|
/// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
|
||||||
|
|||||||
Reference in New Issue
Block a user