Skip to content

Commit 7fc7c7a

Browse files
sonegillis1claude
andcommitted
feat(android): add SSO deep-link support and align Android package id
- Add iblai-skills:// custom URL scheme intent filters via scripts/android-add-deep-link-scheme.sh, wired into the tauri-android-* Makefile targets so SSO deep links route the browser back into the app - Rename Android package from ai.ibl.skills to ai.ibl.skillsai - Register mobile deep-link schemes in tauri.conf.json - Fall back to platform base domain for dm/axd urls in lib/config.ts - Bump app version (tauri 1.0.14, iOS 1.0.7), rename to "Agentic LMS" - Ignore *.apk.idsig and android signing artifacts Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b55c55a commit 7fc7c7a

18 files changed

Lines changed: 3056 additions & 231 deletions

File tree

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,12 @@ playwright/.auth
6464
e2e/playwright/.auth
6565

6666
# tauri builds
67-
*.pkg
67+
*.pkg
68+
*.aab
69+
*.apk
70+
*.apk.idsig
71+
72+
# android signing
73+
*.jks
74+
*.keystore
75+
key.properties

Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
tauri-build-windows tauri-build-windows-arm \
77
tauri-build-linux tauri-build-linux-arm \
88
tauri-android-init tauri-android-dev tauri-android-build tauri-android-build-aab \
9+
android-deep-link-scheme \
910
tauri-ios-init tauri-ios-dev tauri-ios-build \
1011
tauri-info tauri-update tauri-clean tauri-icons \
1112
tauri-build-all-desktop tauri-build-all-mobile
@@ -198,10 +199,17 @@ tauri-build-linux-arm:
198199
# Initialize Android project (run once)
199200
tauri-android-init:
200201
cargo tauri android init
202+
@bash scripts/android-add-deep-link-scheme.sh
203+
204+
# Re-apply the iblai-skills:// custom URL scheme intent filters to the generated
205+
# (gitignored) Android manifest so SSO deep links route the browser back into the
206+
# app. Idempotent; runs automatically before every Android build/dev target.
207+
android-deep-link-scheme:
208+
@bash scripts/android-add-deep-link-scheme.sh
201209

202210
# Run Android dev build (auto-detects local IP for dev server)
203211
# Run 'make dev-mobile' first in another terminal
204-
tauri-android-dev:
212+
tauri-android-dev: android-deep-link-scheme
205213
@LOCAL_IP=$$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null); \
206214
if [ -z "$$LOCAL_IP" ]; then \
207215
echo "Error: Could not detect local IP address"; \
@@ -212,11 +220,11 @@ tauri-android-dev:
212220
--config '{"build":{"devUrl":"http://'"$$LOCAL_IP"':3001","frontendDist":"http://'"$$LOCAL_IP"':3001"}}'
213221

214222
# Build for Android (release APK)
215-
tauri-android-build:
223+
tauri-android-build: android-deep-link-scheme
216224
cargo tauri android build
217225

218226
# Build for Android (release AAB for Play Store)
219-
tauri-android-build-aab:
227+
tauri-android-build-aab: android-deep-link-scheme
220228
cargo tauri android build --aab
221229

222230
# ============================================

lib/config.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,24 @@ export const getEnv = (key: keyof typeof env, fallback = ''): string => {
4444

4545
const apiBaseUrl = () => getEnv('NEXT_PUBLIC_API_BASE_URL', 'https://api.iblai.app');
4646

47+
const platformBaseDomain = () => getEnv('NEXT_PUBLIC_PLATFORM_BASE_DOMAIN', '.iblai.app');
48+
4749
export const config = {
4850
environment: () => getEnv('NODE_ENV', 'development'),
4951
urls: {
5052
apiBase: apiBaseUrl,
51-
dm: () => `${apiBaseUrl()}/dm`,
52-
axd: () => `${apiBaseUrl()}/axd`,
53+
dm: () => {
54+
if (apiBaseUrl) {
55+
return `${apiBaseUrl()}/dm`;
56+
}
57+
return `https://base.manager.${platformBaseDomain()}`;
58+
},
59+
axd: () => {
60+
if (apiBaseUrl) {
61+
return `${apiBaseUrl()}/axd`;
62+
}
63+
return `https://base.manager.${platformBaseDomain()}`;
64+
},
5365
lms: () => getEnv('NEXT_PUBLIC_LMS_URL', 'https://lms.iblai.app'),
5466
legacyLmsUrl: () => getEnv('NEXT_PUBLIC_LMS_URL', 'https://lms.iblai.app'),
5567
studio: () => `${apiBaseUrl()}/studio`,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/bin/bash
2+
# Re-apply the custom URL scheme intent filters to the generated Android manifest.
3+
#
4+
# Why this exists:
5+
# The auth flow finishes by redirecting the system browser to a custom scheme,
6+
# iblai-skills:///sso-login-complete?data=…, which must route back into the app.
7+
# iOS handles this scheme via its WebView delegate; Android requires an explicit
8+
# <intent-filter> in AndroidManifest.xml. Once the OS routes it, the deep-link
9+
# plugin emits the URL and handle_deep_link_url (src-tauri/src/lib.rs) navigates
10+
# the WebView to the real https completion URL.
11+
#
12+
# src-tauri/gen/android is generated and gitignored, so a fresh checkout or a
13+
# `cargo tauri android init` loses the manifest edit. This script re-applies it
14+
# and is wired into the `tauri-android-*` Makefile targets so it runs before
15+
# every Android build. It is idempotent.
16+
set -e
17+
18+
MANIFEST="$(dirname "$0")/../src-tauri/gen/android/app/src/main/AndroidManifest.xml"
19+
20+
if [ ! -f "$MANIFEST" ]; then
21+
echo "⚠ Android manifest not found at: $MANIFEST"
22+
echo " Run 'cargo tauri android init' (make tauri-android-init) first."
23+
exit 0
24+
fi
25+
26+
if grep -q 'android:scheme="iblai-skills"' "$MANIFEST"; then
27+
echo "✓ Custom-scheme intent filters already present in AndroidManifest.xml — nothing to do."
28+
exit 0
29+
fi
30+
31+
echo "→ Injecting iblai-skills / ai.ibl.skills intent filters into AndroidManifest.xml…"
32+
33+
# Insert the filters immediately before the closing </activity> of MainActivity
34+
# (the manifest has a single <activity>).
35+
awk '
36+
/<\/activity>/ && !done {
37+
print " <!-- Custom URL scheme for the SSO deep-link return from the"
38+
print " system browser (iblai-skills:///sso-login-complete?data=…)."
39+
print " iOS handles this scheme via its WebView delegate; Android"
40+
print " needs an explicit intent filter so the OS routes the redirect"
41+
print " back into the app. Re-applied by"
42+
print " scripts/android-add-deep-link-scheme.sh because gen/android is"
43+
print " generated/gitignored. Custom schemes cannot use autoVerify. -->"
44+
print " <intent-filter>"
45+
print " <action android:name=\"android.intent.action.VIEW\" />"
46+
print " <category android:name=\"android.intent.category.DEFAULT\" />"
47+
print " <category android:name=\"android.intent.category.BROWSABLE\" />"
48+
print " <data android:scheme=\"iblai-skills\" />"
49+
print " </intent-filter>"
50+
print " <intent-filter>"
51+
print " <action android:name=\"android.intent.action.VIEW\" />"
52+
print " <category android:name=\"android.intent.category.DEFAULT\" />"
53+
print " <category android:name=\"android.intent.category.BROWSABLE\" />"
54+
print " <data android:scheme=\"ai.ibl.skills\" />"
55+
print " </intent-filter>"
56+
done = 1
57+
}
58+
{ print }
59+
' "$MANIFEST" > "$MANIFEST.tmp" && mv "$MANIFEST.tmp" "$MANIFEST"
60+
61+
echo "✓ Intent filters injected into AndroidManifest.xml."

src-tauri/entitlements.mac.plist

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5-
<!-- Application identifier (must match provisioning profile) -->
65
<key>com.apple.application-identifier</key>
76
<string>L4FWRM8W5Z.ai.ibl.skillsai</string>
87

9-
<!-- Team identifier -->
10-
<key>com.apple.developer.team-identifier</key>
11-
<string>L4FWRM8W5Z</string>
12-
138
<!-- Required for Mac App Store -->
149
<key>com.apple.security.app-sandbox</key>
1510
<true/>

src-tauri/gen/android/app/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/src/main/java/ai/ibl/skills/generated
1+
/src/main/java/ai/ibl/skillsai/generated
22
/src/main/jniLibs/**/*.so
33
/src/main/assets/tauri.conf.json
44
/tauri.build.gradle.kts

src-tauri/gen/android/app/build.gradle.kts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,12 @@ val tauriProperties = Properties().apply {
1313
}
1414
}
1515

16-
val keyProperties = Properties().apply {
17-
val propFile = rootProject.file("key.properties")
18-
if (propFile.exists()) {
19-
propFile.inputStream().use { load(it) }
20-
}
21-
}
22-
2316
android {
2417
compileSdk = 36
25-
namespace = "ai.ibl.skills"
26-
27-
signingConfigs {
28-
create("release") {
29-
val ksFile = keyProperties.getProperty("storeFile")
30-
if (ksFile != null) {
31-
storeFile = file(ksFile)
32-
storePassword = keyProperties.getProperty("storePassword")
33-
keyAlias = keyProperties.getProperty("keyAlias")
34-
keyPassword = keyProperties.getProperty("keyPassword")
35-
}
36-
}
37-
}
38-
18+
namespace = "ai.ibl.skillsai"
3919
defaultConfig {
4020
manifestPlaceholders["usesCleartextTraffic"] = "false"
41-
applicationId = "ai.ibl.skills"
21+
applicationId = "ai.ibl.skillsai"
4222
minSdk = 24
4323
targetSdk = 36
4424
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
@@ -57,7 +37,6 @@ android {
5737
}
5838
}
5939
getByName("release") {
60-
signingConfig = signingConfigs.getByName("release")
6140
isMinifyEnabled = true
6241
proguardFiles(
6342
*fileTree(".") { include("**/*.pro") }
@@ -72,11 +51,6 @@ android {
7251
buildFeatures {
7352
buildConfig = true
7453
}
75-
packaging {
76-
jniLibs {
77-
useLegacyPackaging = false
78-
}
79-
}
8054
}
8155

8256
rust {

src-tauri/gen/android/app/src/main/AndroidManifest.xml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,41 @@
2222
<!-- AndroidTV support -->
2323
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
2424
</intent-filter>
25+
<!-- Custom URL scheme for the SSO deep-link return from the
26+
system browser (iblai-skills:///sso-login-complete?data=…).
27+
iOS handles this scheme via its WebView delegate; Android
28+
needs an explicit intent filter so the OS routes the redirect
29+
back into the app. Re-applied by
30+
scripts/android-add-deep-link-scheme.sh because gen/android is
31+
generated/gitignored. Custom schemes cannot use autoVerify. -->
2532
<intent-filter>
2633
<action android:name="android.intent.action.VIEW" />
2734
<category android:name="android.intent.category.DEFAULT" />
2835
<category android:name="android.intent.category.BROWSABLE" />
2936
<data android:scheme="iblai-skills" />
3037
</intent-filter>
38+
<intent-filter>
39+
<action android:name="android.intent.action.VIEW" />
40+
<category android:name="android.intent.category.DEFAULT" />
41+
<category android:name="android.intent.category.BROWSABLE" />
42+
<data android:scheme="ai.ibl.skills" />
43+
</intent-filter>
44+
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
45+
<intent-filter >
46+
<action android:name="android.intent.action.VIEW" />
47+
<!-- ChromeOS ARC++ uses a different action for deep links -->
48+
<action android:name="org.chromium.arc.intent.action.VIEW" />
49+
<category android:name="android.intent.category.DEFAULT" />
50+
<category android:name="android.intent.category.BROWSABLE" />
51+
<data android:scheme="iblai-skills" />
52+
<data android:scheme="ai.ibl.skills" />
53+
54+
55+
56+
57+
58+
</intent-filter>
59+
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
3160
</activity>
3261

3362
<provider

src-tauri/gen/android/app/src/main/java/ai/ibl/skills/MainActivity.kt renamed to src-tauri/gen/android/app/src/main/java/ai/ibl/skillsai/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
package ai.ibl.skills
1+
package ai.ibl.skillsai
22

33
class MainActivity : TauriActivity()

src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)