capacitor-deep-linking by cap-go/capgo-skills
npx skills add https://github.com/cap-go/capgo-skills --skill capacitor-deep-linking在 Capacitor 应用中实现深度链接、通用链接和应用链接。
| 类型 | 平台 | 格式 | 需要服务器 |
|---|---|---|---|
| 自定义 URL 方案 | 两者皆可 | myapp://path | 否 |
| 通用链接 | iOS | https://myapp.com/path | 是 |
| 应用链接 | Android | https://myapp.com/path |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 是 |
bun add @capacitor/app
bunx cap sync
import { App } from '@capacitor/app';
// 监听深度链接打开事件
App.addListener('appUrlOpen', (event) => {
console.log('应用通过 URL 打开:', event.url);
// 解析并导航
const url = new URL(event.url);
handleDeepLink(url);
});
function handleDeepLink(url: URL) {
// 自定义方案:myapp://product/123
// 通用链接:https://myapp.com/product/123
const path = url.pathname || url.host + url.pathname;
// 根据路径路由
if (path.startsWith('/product/')) {
const productId = path.split('/')[2];
navigateTo(`/product/${productId}`);
} else if (path.startsWith('/user/')) {
const userId = path.split('/')[2];
navigateTo(`/profile/${userId}`);
} else if (path === '/login') {
navigateTo('/login');
} else {
navigateTo('/');
}
}
<!-- ios/App/App/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
<string>myapp-dev</string>
</array>
</dict>
</array>
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<!-- 深度链接意图过滤器 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
# iOS 模拟器
xcrun simctl openurl booted "myapp://product/123"
# Android
adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"
在 Xcode 中:
* Capability > Associated Domains
applinks:myapp.com托管在 https://myapp.com/.well-known/apple-app-site-association:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.yourcompany.yourapp",
"paths": [
"/product/*",
"/user/*",
"/invite/*",
"NOT /api/*"
]
}
]
}
}
要求:
application/json<!-- ios/App/App/Info.plist -->
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.com</string>
<string>applinks:www.myapp.com</string>
</array>
# 验证 AASA 文件
curl -I https://myapp.com/.well-known/apple-app-site-association
# 检查 Apple CDN 缓存
curl "https://app-site-association.cdn-apple.com/a/v1/myapp.com"
托管在 https://myapp.com/.well-known/assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}
]
# 调试密钥库
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# 发布密钥库
keytool -list -v -keystore release.keystore -alias your-alias
# 从 APK 获取
keytool -printcert -jarfile app-release.apk
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<!-- 应用链接意图过滤器 -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="myapp.com" />
<data android:pathPrefix="/product" />
<data android:pathPrefix="/user" />
<data android:pathPrefix="/invite" />
</intent-filter>
</activity>
# 验证 assetlinks.json 文件
curl https://myapp.com/.well-known/assetlinks.json
# 使用 Google 的验证工具
https://developers.google.com/digital-asset-links/tools/generator
# 检查设备上的链接处理
adb shell pm get-app-links com.yourcompany.yourapp
import { App } from '@capacitor/app';
import { useHistory } from 'react-router-dom';
import { useEffect } from 'react';
function DeepLinkHandler() {
const history = useHistory();
useEffect(() => {
App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
const path = getPathFromUrl(url);
// 使用 React Router 导航
history.push(path);
});
// 检查应用是否通过 URL 打开
App.getLaunchUrl().then((result) => {
if (result?.url) {
const url = new URL(result.url);
const path = getPathFromUrl(url);
history.push(path);
}
});
}, []);
return null;
}
function getPathFromUrl(url: URL): string {
// 处理自定义方案和 https
if (url.protocol === 'myapp:') {
return '/' + url.host + url.pathname;
}
return url.pathname + url.search;
}
import { App } from '@capacitor/app';
import { useRouter } from 'vue-router';
import { onMounted } from 'vue';
export function useDeepLinks() {
const router = useRouter();
onMounted(async () => {
App.addListener('appUrlOpen', (event) => {
const path = parseDeepLink(event.url);
router.push(path);
});
const launchUrl = await App.getLaunchUrl();
if (launchUrl?.url) {
const path = parseDeepLink(launchUrl.url);
router.push(path);
}
});
}
处理应用未安装时的链接:
import { App } from '@capacitor/app';
import { Preferences } from '@capacitor/preferences';
// 在首次启动时,检查延迟链接
async function checkDeferredDeepLink() {
const { value: isFirstLaunch } = await Preferences.get({ key: 'firstLaunch' });
if (isFirstLaunch !== 'false') {
await Preferences.set({ key: 'firstLaunch', value: 'false' });
// 通过你的归因服务检查
const deferredLink = await fetchDeferredLink();
if (deferredLink) {
handleDeepLink(new URL(deferredLink));
}
}
}
App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
// 获取查询参数
const source = url.searchParams.get('source');
const campaign = url.searchParams.get('campaign');
const referrer = url.searchParams.get('ref');
// 跟踪归因
analytics.logEvent('deep_link_open', {
path: url.pathname,
source,
campaign,
referrer,
});
// 携带状态导航
navigateTo(url.pathname, {
state: { source, campaign, referrer },
});
});
// 处理 OAuth 重定向
App.addListener('appUrlOpen', async (event) => {
const url = new URL(event.url);
if (url.pathname === '/oauth/callback') {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const error = url.searchParams.get('error');
if (error) {
handleOAuthError(error);
return;
}
if (code && validateState(state)) {
await exchangeCodeForToken(code);
navigateTo('/home');
}
}
});
| 场景 | 命令 |
|---|---|
| 自定义方案 | myapp://path |
| 通用链接冷启动 | 应用关闭时点击链接 |
| 通用链接热启动 | 应用在后台时点击链接 |
| Safari 中的通用链接 | 在 Safari 中输入 URL |
| 应用链接冷启动 | 应用关闭时点击链接 |
| Chrome 中的应用链接 | 在 Chrome 中点击链接 |
# iOS:检查关联域名权限
codesign -d --entitlements - App.app | grep associated-domains
# iOS:重置通用链接缓存
xcrun simctl erase all
# Android:检查已验证的链接
adb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"
| 问题 | 解决方案 |
|---|---|
| 通用链接不工作 | 检查 AASA 文件、SSL、权限 |
| 应用链接未验证 | 检查 assetlinks.json 文件、指纹 |
| 链接在浏览器中打开 | 检查 intent-filter、autoVerify |
| 冷启动未处理 | 使用 App.getLaunchUrl() |
| 模拟器问题 | 重置模拟器,重新构建应用 |
每周安装量
64
代码仓库
GitHub 星标数
18
首次出现
2026年2月6日
安全审计
安装于
gemini-cli62
opencode60
github-copilot58
codex58
amp56
kimi-cli56
Implement deep links, universal links, and app links in Capacitor apps.
| Type | Platform | Format | Requires Server |
|---|---|---|---|
| Custom URL Scheme | Both | myapp://path | No |
| Universal Links | iOS | https://myapp.com/path | Yes |
| App Links | Android | https://myapp.com/path | Yes |
bun add @capacitor/app
bunx cap sync
import { App } from '@capacitor/app';
// Listen for deep link opens
App.addListener('appUrlOpen', (event) => {
console.log('App opened with URL:', event.url);
// Parse and navigate
const url = new URL(event.url);
handleDeepLink(url);
});
function handleDeepLink(url: URL) {
// Custom scheme: myapp://product/123
// Universal link: https://myapp.com/product/123
const path = url.pathname || url.host + url.pathname;
// Route based on path
if (path.startsWith('/product/')) {
const productId = path.split('/')[2];
navigateTo(`/product/${productId}`);
} else if (path.startsWith('/user/')) {
const userId = path.split('/')[2];
navigateTo(`/profile/${userId}`);
} else if (path === '/login') {
navigateTo('/login');
} else {
navigateTo('/');
}
}
<!-- ios/App/App/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
<string>myapp-dev</string>
</array>
</dict>
</array>
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<!-- Deep link intent filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
# iOS Simulator
xcrun simctl openurl booted "myapp://product/123"
# Android
adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"
In Xcode:
* Capability > Associated Domains
applinks:myapp.comHost at https://myapp.com/.well-known/apple-app-site-association:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.yourcompany.yourapp",
"paths": [
"/product/*",
"/user/*",
"/invite/*",
"NOT /api/*"
]
}
]
}
}
Requirements :
application/json<!-- ios/App/App/Info.plist -->
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.com</string>
<string>applinks:www.myapp.com</string>
</array>
# Validate AASA file
curl -I https://myapp.com/.well-known/apple-app-site-association
# Check Apple CDN cache
curl "https://app-site-association.cdn-apple.com/a/v1/myapp.com"
Host at https://myapp.com/.well-known/assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}
]
# Debug keystore
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Release keystore
keytool -list -v -keystore release.keystore -alias your-alias
# From APK
keytool -printcert -jarfile app-release.apk
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<!-- App Links intent filter -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="myapp.com" />
<data android:pathPrefix="/product" />
<data android:pathPrefix="/user" />
<data android:pathPrefix="/invite" />
</intent-filter>
</activity>
# Validate assetlinks.json
curl https://myapp.com/.well-known/assetlinks.json
# Use Google's validator
https://developers.google.com/digital-asset-links/tools/generator
# Check link handling on device
adb shell pm get-app-links com.yourcompany.yourapp
import { App } from '@capacitor/app';
import { useHistory } from 'react-router-dom';
import { useEffect } from 'react';
function DeepLinkHandler() {
const history = useHistory();
useEffect(() => {
App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
const path = getPathFromUrl(url);
// Navigate using React Router
history.push(path);
});
// Check if app was opened with URL
App.getLaunchUrl().then((result) => {
if (result?.url) {
const url = new URL(result.url);
const path = getPathFromUrl(url);
history.push(path);
}
});
}, []);
return null;
}
function getPathFromUrl(url: URL): string {
// Handle both custom scheme and https
if (url.protocol === 'myapp:') {
return '/' + url.host + url.pathname;
}
return url.pathname + url.search;
}
import { App } from '@capacitor/app';
import { useRouter } from 'vue-router';
import { onMounted } from 'vue';
export function useDeepLinks() {
const router = useRouter();
onMounted(async () => {
App.addListener('appUrlOpen', (event) => {
const path = parseDeepLink(event.url);
router.push(path);
});
const launchUrl = await App.getLaunchUrl();
if (launchUrl?.url) {
const path = parseDeepLink(launchUrl.url);
router.push(path);
}
});
}
Handle links when app wasn't installed:
import { App } from '@capacitor/app';
import { Preferences } from '@capacitor/preferences';
// On first launch, check for deferred link
async function checkDeferredDeepLink() {
const { value: isFirstLaunch } = await Preferences.get({ key: 'firstLaunch' });
if (isFirstLaunch !== 'false') {
await Preferences.set({ key: 'firstLaunch', value: 'false' });
// Check with your attribution service
const deferredLink = await fetchDeferredLink();
if (deferredLink) {
handleDeepLink(new URL(deferredLink));
}
}
}
App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
// Get query parameters
const source = url.searchParams.get('source');
const campaign = url.searchParams.get('campaign');
const referrer = url.searchParams.get('ref');
// Track attribution
analytics.logEvent('deep_link_open', {
path: url.pathname,
source,
campaign,
referrer,
});
// Navigate with state
navigateTo(url.pathname, {
state: { source, campaign, referrer },
});
});
// Handle OAuth redirect
App.addListener('appUrlOpen', async (event) => {
const url = new URL(event.url);
if (url.pathname === '/oauth/callback') {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const error = url.searchParams.get('error');
if (error) {
handleOAuthError(error);
return;
}
if (code && validateState(state)) {
await exchangeCodeForToken(code);
navigateTo('/home');
}
}
});
| Scenario | Command |
|---|---|
| Custom scheme | myapp://path |
| Universal link cold start | Tap link with app closed |
| Universal link warm start | Tap link with app in background |
| Universal link in Safari | Type URL in Safari |
| App link cold start | Tap link with app closed |
| App link in Chrome | Tap link in Chrome |
# iOS: Check associated domains entitlement
codesign -d --entitlements - App.app | grep associated-domains
# iOS: Reset Universal Links cache
xcrun simctl erase all
# Android: Check verified links
adb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"
| Issue | Solution |
|---|---|
| Universal Links not working | Check AASA file, SSL, entitlements |
| App Links not verified | Check assetlinks.json, fingerprint |
| Links open in browser | Check intent-filter, autoVerify |
| Cold start not handled | Use App.getLaunchUrl() |
| Simulator issues | Reset simulator, rebuild app |
Weekly Installs
64
Repository
GitHub Stars
18
First Seen
Feb 6, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
gemini-cli62
opencode60
github-copilot58
codex58
amp56
kimi-cli56
Flutter应用架构设计指南:分层结构、数据层实现与最佳实践
4,800 周安装