[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
---
|
||||
id: ios-universal-links-deep-linking
|
||||
title: iOS Universal Links — 웹 URL → 앱 진입
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [ios, deep-link, universal-link, vibe-coding]
|
||||
tech_stack: { language: "Swift", applicable_to: ["iOS"] }
|
||||
applied_in: []
|
||||
aliases: [associated domains, AASA, custom URL scheme, deferred deep link]
|
||||
---
|
||||
|
||||
# iOS Universal Links
|
||||
|
||||
> `https://example.com/order/123` → 앱 설치되어 있으면 앱이 처리, 아니면 웹. **AASA 파일 + Associated Domains capability** 필수. 옛 custom scheme (`myapp://`) 보다 안전.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- AASA (apple-app-site-association): 도메인이 어떤 앱과 연결.
|
||||
- Associated Domains capability: 앱에 도메인 등록.
|
||||
- onContinueUserActivity: SwiftUI / SceneDelegate.
|
||||
- Deferred deep link: 앱 설치 후 첫 실행에 의도 복원 — 별도 SDK (Branch, Adjust).
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### 1. 서버 — AASA
|
||||
```json
|
||||
// https://example.com/.well-known/apple-app-site-association
|
||||
{
|
||||
"applinks": {
|
||||
"details": [
|
||||
{
|
||||
"appIDs": ["TEAMID.com.example.MyApp"],
|
||||
"components": [
|
||||
{ "/": "/order/*" },
|
||||
{ "/": "/user/*", "?": { "ref": "?*" } },
|
||||
{ "/": "/admin/*", "exclude": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
응답 헤더: `Content-Type: application/json`. 인증 / redirect X.
|
||||
|
||||
### 2. 앱 — Associated Domains
|
||||
- Xcode → Signing & Capabilities → Associated Domains.
|
||||
- `applinks:example.com` 추가.
|
||||
|
||||
### 3. SwiftUI — handle
|
||||
```swift
|
||||
@main
|
||||
struct MyApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
|
||||
guard let url = activity.webpageURL else { return }
|
||||
handle(url)
|
||||
}
|
||||
.onOpenURL { url in
|
||||
// Custom scheme (myapp://) 또는 system 호출
|
||||
handle(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handle(_ url: URL) {
|
||||
let comps = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
||||
guard let path = comps?.path else { return }
|
||||
if path.hasPrefix("/order/") {
|
||||
let id = String(path.dropFirst("/order/".count))
|
||||
navigationStore.navigate(.order(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. UIKit — SceneDelegate
|
||||
```swift
|
||||
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
|
||||
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
|
||||
let url = userActivity.webpageURL { handle(url) }
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
|
||||
if let url = contexts.first?.url { handle(url) }
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Custom URL scheme (legacy)
|
||||
```xml
|
||||
<!-- Info.plist -->
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array><string>myapp</string></array>
|
||||
</dict>
|
||||
</array>
|
||||
```
|
||||
|
||||
`myapp://order/123` — but Universal Links 권장 (위조 가능 / 사용자 confusion).
|
||||
|
||||
### 6. Deferred deep link
|
||||
- 앱 미설치 → App Store 이동 → 설치 후 첫 실행 시 원래 의도 복원.
|
||||
- iOS 자체 지원 X — Branch.io / Adjust / AppsFlyer SDK.
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 도구 |
|
||||
|---|---|
|
||||
| 외부 URL → 앱 진입 | Universal Links |
|
||||
| OS 가 호출 (인증 callback) | Universal Link 또는 Custom scheme |
|
||||
| Push notification 안 deep link | userInfo + handle() |
|
||||
| 위젯 → 앱 deep link | widgetURL |
|
||||
| 앱 설치 전 URL 의 의도 복원 | Deferred (외부 SDK) |
|
||||
| QR / NFC | App Clip + invocation URL |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **AASA 가 redirect 또는 401**: 앱 인식 못 함. CDN / 로컬 호스팅 정확히.
|
||||
- **AASA 너무 큼 (1MB+)**: 다운로드 안 됨. 작게.
|
||||
- **Universal Link 가 같은 도메인의 사파리에서 호출**: 앱 안 열림. 다른 앱에서 들어와야.
|
||||
- **handle 가 navigation 만 — 인증 검증 누락**: 권한 우회 가능.
|
||||
- **스킴만 의존**: 위조 가능, 다른 앱이 같은 scheme 등록 가능.
|
||||
- **path / params 검증 없이 직접 사용**: SQL/XSS 인젝션 가능.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- AASA + Associated Domains + onContinueUserActivity 3종.
|
||||
- path / params 검증 (zod-like) 후 navigation.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[iOS_App_Clips]]
|
||||
- [[iOS_Push_Notifications]]
|
||||
Reference in New Issue
Block a user