Skip to content

Commit 75617a6

Browse files
fix(mobile): deeplinks (#2870)
* feat: android deeplinks * feat: explicit app link declarations * feat: add ios code * fix: add ios deeplink adaptation * feat: ios working (some swift plugin api improvements needed) * fix: revert ios to prior logic * fix(cleanup): regen android files with old names * fix: web link criteria * fix: conditional auto verify intent filter for android app links * fix: default to true * fix: typo * fix: pnpm version * cleanup * fix: web link regression * trim androidmanifest update * fix deep link validation broken due to appLink=true default * implement update_info_plist from tauri-apps/tauri#13888 * fix: remove old patch crates * fix: use latest patch tauri * lint --------- Co-authored-by: Lucas Nogueira <[email protected]>
1 parent 5a963a0 commit 75617a6

File tree

21 files changed

+331
-195
lines changed

21 files changed

+331
-195
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/deep-link/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ serde_json = { workspace = true }
2727
tauri-utils = { workspace = true }
2828
tauri-plugin = { workspace = true, features = ["build"] }
2929

30+
[target."cfg(target_os = \"macos\")".build-dependencies]
31+
plist = "1"
32+
3033
[dependencies]
3134
serde = { workspace = true }
3235
serde_json = { workspace = true }

plugins/deep-link/build.rs

Lines changed: 119 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,64 @@ const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registe
1010

1111
// TODO: Consider using activity-alias in case users may have multiple activities in their app.
1212
fn intent_filter(domain: &AssociatedDomain) -> String {
13+
let host = domain
14+
.host
15+
.as_ref()
16+
.map(|h| format!(r#"<data android:host="{h}" />"#))
17+
.unwrap_or_default();
18+
19+
let auto_verify = if domain.is_app_link() {
20+
r#"android:autoVerify="true" "#.to_string()
21+
} else {
22+
String::new()
23+
};
24+
1325
format!(
14-
r#"<intent-filter android:autoVerify="true">
26+
r#"<intent-filter {auto_verify}>
1527
<action android:name="android.intent.action.VIEW" />
1628
<category android:name="android.intent.category.DEFAULT" />
1729
<category android:name="android.intent.category.BROWSABLE" />
18-
{}
19-
<data android:host="{}" />
20-
{}
21-
{}
22-
{}
23-
{}
30+
{schemes}
31+
{host}
32+
{domains}
33+
{path_patterns}
34+
{path_prefixes}
35+
{path_suffixes}
2436
</intent-filter>"#,
25-
domain
37+
schemes = domain
2638
.scheme
2739
.iter()
2840
.map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#))
2941
.collect::<Vec<_>>()
3042
.join("\n "),
31-
domain.host,
32-
domain
43+
host = host,
44+
domains = domain
3345
.path
3446
.iter()
3547
.map(|path| format!(r#"<data android:path="{path}" />"#))
3648
.collect::<Vec<_>>()
3749
.join("\n "),
38-
domain
50+
path_patterns = domain
3951
.path_pattern
4052
.iter()
4153
.map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#))
4254
.collect::<Vec<_>>()
4355
.join("\n "),
44-
domain
56+
path_prefixes = domain
4557
.path_prefix
4658
.iter()
4759
.map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#))
4860
.collect::<Vec<_>>()
4961
.join("\n "),
50-
domain
62+
path_suffixes = domain
5163
.path_suffix
5264
.iter()
5365
.map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#))
5466
.collect::<Vec<_>>()
5567
.join("\n "),
5668
)
69+
.trim()
70+
.to_string()
5771
}
5872

5973
fn main() {
@@ -68,6 +82,16 @@ fn main() {
6882
}
6983

7084
if let Some(config) = tauri_plugin::plugin_config::<Config>("deep-link") {
85+
let errors: Vec<String> = config
86+
.mobile
87+
.iter()
88+
.filter_map(|d| d.validate().err())
89+
.collect();
90+
91+
if !errors.is_empty() {
92+
panic!("Deep link config validation failed:\n{}", errors.join("\n"));
93+
}
94+
7195
tauri_plugin::mobile::update_android_manifest(
7296
"DEEP LINK PLUGIN",
7397
"activity",
@@ -80,20 +104,89 @@ fn main() {
80104
)
81105
.expect("failed to rewrite AndroidManifest.xml");
82106

83-
#[cfg(target_os = "macos")]
107+
#[cfg(any(target_os = "macos", target_os = "ios"))]
84108
{
85-
tauri_plugin::mobile::update_entitlements(|entitlements| {
86-
entitlements.insert(
87-
"com.apple.developer.associated-domains".into(),
88-
config
89-
.mobile
90-
.into_iter()
91-
.map(|d| format!("applinks:{}", d.host).into())
92-
.collect::<Vec<_>>()
93-
.into(),
94-
);
95-
})
96-
.expect("failed to update entitlements");
109+
// we need to ensure that the entitlements are only
110+
// generated for explicit app links and not
111+
// other deep links because then they
112+
// are just going to complain and not be built or signed
113+
let has_app_links = config.mobile.iter().any(|d| d.is_app_link());
114+
115+
if !has_app_links {
116+
tauri_plugin::mobile::update_entitlements(|entitlements| {
117+
entitlements.remove("com.apple.developer.associated-domains");
118+
})
119+
.expect("failed to update entitlements");
120+
} else {
121+
tauri_plugin::mobile::update_entitlements(|entitlements| {
122+
entitlements.insert(
123+
"com.apple.developer.associated-domains".into(),
124+
config
125+
.mobile
126+
.iter()
127+
.filter(|d| d.is_app_link())
128+
.filter_map(|d| d.host.as_ref())
129+
.map(|host| format!("applinks:{}", host).into())
130+
.collect::<Vec<_>>()
131+
.into(),
132+
);
133+
})
134+
.expect("failed to update entitlements");
135+
}
136+
137+
let deep_link_domains = config
138+
.mobile
139+
.iter()
140+
.filter_map(|domain| {
141+
if domain.is_app_link() {
142+
return None;
143+
}
144+
145+
Some(domain)
146+
})
147+
.collect::<Vec<_>>();
148+
149+
if deep_link_domains.is_empty() {
150+
tauri_plugin::mobile::update_info_plist(|info_plist| {
151+
info_plist.remove("CFBundleURLTypes");
152+
})
153+
.expect("failed to update Info.plist");
154+
} else {
155+
tauri_plugin::mobile::update_info_plist(|info_plist| {
156+
info_plist.insert(
157+
"CFBundleURLTypes".into(),
158+
deep_link_domains
159+
.iter()
160+
.map(|domain| {
161+
let schemes = domain
162+
.scheme
163+
.iter()
164+
.filter(|scheme| {
165+
scheme.as_str() != "https" && scheme.as_str() != "http"
166+
})
167+
.collect::<Vec<_>>();
168+
169+
let mut dict = plist::Dictionary::new();
170+
dict.insert(
171+
"CFBundleURLSchemes".into(),
172+
schemes
173+
.iter()
174+
.map(|s| s.to_string().into())
175+
.collect::<Vec<_>>()
176+
.into(),
177+
);
178+
dict.insert(
179+
"CFBundleURLName".into(),
180+
format!("{}", domain.scheme[0]).into(),
181+
);
182+
plist::Value::Dictionary(dict)
183+
})
184+
.collect::<Vec<_>>()
185+
.into(),
186+
);
187+
})
188+
.expect("failed to update Info.plist");
189+
}
97190
}
98191
}
99192
}

plugins/deep-link/examples/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212
"dependencies": {
1313
"@tauri-apps/api": "2.8.0",
14-
"@tauri-apps/plugin-deep-link": "2.4.1"
14+
"@tauri-apps/plugin-deep-link": "link:../.."
1515
},
1616
"devDependencies": {
1717
"@tauri-apps/cli": "2.8.1",

plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
1414
}
1515

1616
android {
17-
compileSdk = 34
17+
compileSdk = 36
1818
namespace = "com.tauri.deep_link_example"
1919
defaultConfig {
2020
manifestPlaceholders["usesCleartextTraffic"] = "false"
2121
applicationId = "com.tauri.deep_link_example"
2222
minSdk = 24
23-
targetSdk = 34
23+
targetSdk = 36
2424
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
2525
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
2626
}
@@ -58,9 +58,10 @@ rust {
5858
}
5959

6060
dependencies {
61-
implementation("androidx.webkit:webkit:1.6.1")
62-
implementation("androidx.appcompat:appcompat:1.6.1")
63-
implementation("com.google.android.material:material:1.8.0")
61+
implementation("androidx.webkit:webkit:1.14.0")
62+
implementation("androidx.appcompat:appcompat:1.7.1")
63+
implementation("androidx.activity:activity-ktx:1.10.1")
64+
implementation("com.google.android.material:material:1.12.0")
6465
testImplementation("junit:junit:4.13.2")
6566
androidTestImplementation("androidx.test.ext:junit:1.1.4")
6667
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")

plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,40 @@
2323
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
2424
</intent-filter>
2525
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
26-
<intent-filter android:autoVerify="true">
26+
<intent-filter android:autoVerify="true" >
2727
<action android:name="android.intent.action.VIEW" />
2828
<category android:name="android.intent.category.DEFAULT" />
2929
<category android:name="android.intent.category.BROWSABLE" />
30-
<data android:scheme="http" />
3130
<data android:scheme="https" />
31+
<data android:scheme="http" />
3232
<data android:host="fabianlars.de" />
33+
34+
3335
<data android:pathPrefix="/intent" />
36+
3437
</intent-filter>
35-
<intent-filter android:autoVerify="true">
38+
<intent-filter android:autoVerify="true" >
3639
<action android:name="android.intent.action.VIEW" />
3740
<category android:name="android.intent.category.DEFAULT" />
3841
<category android:name="android.intent.category.BROWSABLE" />
39-
<data android:scheme="http" />
4042
<data android:scheme="https" />
43+
<data android:scheme="http" />
4144
<data android:host="tauri.app" />
4245

46+
47+
48+
49+
</intent-filter>
50+
<intent-filter >
51+
<action android:name="android.intent.action.VIEW" />
52+
<category android:name="android.intent.category.DEFAULT" />
53+
<category android:name="android.intent.category.BROWSABLE" />
54+
<data android:scheme="taurideeplink" />
55+
56+
57+
58+
59+
4360
</intent-filter>
4461
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
4562
</activity>
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2-
// SPDX-License-Identifier: Apache-2.0
3-
// SPDX-License-Identifier: MIT
4-
51
package com.tauri.deep_link_example
62

7-
class MainActivity : TauriActivity()
3+
import android.os.Bundle
4+
import androidx.activity.enableEdgeToEdge
5+
6+
class MainActivity : TauriActivity() {
7+
override fun onCreate(savedInstanceState: Bundle?) {
8+
enableEdgeToEdge()
9+
super.onCreate(savedInstanceState)
10+
}
11+
}

plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ buildscript {
44
mavenCentral()
55
}
66
dependencies {
7-
classpath("com.android.tools.build:gradle:8.5.1")
7+
classpath("com.android.tools.build:gradle:8.11.0")
88
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
99
}
1010
}

plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ repositories {
1818

1919
dependencies {
2020
compileOnly(gradleApi())
21-
implementation("com.android.tools.build:gradle:8.5.1")
21+
implementation("com.android.tools.build:gradle:8.11.0")
2222
}
2323

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Tue May 10 19:22:52 CST 2022
22
distributionBase=GRADLE_USER_HOME
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
44
distributionPath=wrapper/dists
55
zipStorePath=wrapper/dists
66
zipStoreBase=GRADLE_USER_HOME

0 commit comments

Comments
 (0)