# iOS Universal Links 調査

@startuml
title Universal Links 技術フロー(関連付け~実行)

actor User
participant "Link Source\n(メッセージ/メール/SNS/ブラウザ等)" as Source
participant "iOS" as SWCD
participant "Apple CDN" as CDN
participant "Web Server\n(Origin)" as Web
participant "Safari"
participant "iOS App" as App
participant "App Store" as Store

== 初回の関連付け(アプリ導入直後/初回起動時など) ==
note right of App
Associated Domains に
applinks:example.com
(開発時は ?mode=developer 併用可)
end note

alt 開発モード(?mode=developer)
  SWCD -> Web: GET /.well-known/apple-app-site-association
  Web --> SWCD: 200 application/json(AASA)
else 本番(リリース)
  SWCD -> CDN: GET /a/v1/example.com
  CDN -> Web: (必要に応じてオリジン取得/更新)
  CDN --> SWCD: 200 application/json(AASA)
end
SWCD -> SWCD: AASA検証(appID / paths, components 等)
SWCD --> App: 関連付け結果を登録

== ユーザーがユニバーサルリンクをタップ ==
User -> Source: タップ https://example.com/...
Source -> SWCD: ユニバーサルリンク解決要求
SWCD -> SWCD: ドメイン/パスをAASAで照合

alt アプリがインストール済み かつ パス一致
  SWCD --> App: open via NSUserActivityTypeBrowsingWeb
  App -> App: DeepLinkRouter.handle(url)
  note right of App
  SceneDelegate / AppDelegate / SwiftUI の
  onContinueUserActivity で受信し画面遷移
  end note
else 未インストール または パス不一致
  SWCD --> Safari: https://example.com/... を開く
  opt Web側でSmart App Banner等を表示(任意)
    Safari -> Store: Appページを開く(ユーザー操作)
  end
end

== ユーザーの既定動作 ==
opt ユーザーが以前「Safariで開く」を選択
  note right of Safari
  同一リンクは以後Safari優先
  (長押しで「Appで開く」を再選択可)
  end note
end

== 厳守事項(AASA配信) ==
note over Web
・HTTPS必須、200直返し、拡張子なし
・Content-Type: application/json
・AASAパスでのリダイレクト禁止
end note
@enduml

# 呼び出しアプリ→ユニバーサルリンク→受け入れアプリ 呼び出しフロー

@startuml
title 呼び出しアプリ→ユニバーサルリンク→受け入れアプリ 呼び出しフロー

actor User
participant "呼び出しアプリ" as B
participant "iOS (System / swcd)" as iOS
participant "AASA配信\n(Apple CDN / Web)" as AASA
participant "受け入れアプリ" as A
participant "Safari" as Safari
participant "App Store" as Store

User -> B: ボタンタップ「受け入れアプリを開く」
B -> B: ユニバーサルリンクURL生成(https://example.com/...note right of B #FFEECC
A.要アプリ実装(ボタン処理とURL生成)
end note
B -> iOS: UIApplication.open(url)
note right of B #FFEECC
B.要アプリ実装(open呼び出しとフォールバック)
end note

iOS -> iOS: AASA照合(キャッシュ)
iOS -> AASA: ※必要時のみAASA取得(簡易)
AASA --> iOS: 200 JSON(簡易)
note over AASA #FFDDE0
F.要サーバー実装(AASAをHTTPS/200 JSONで配信)
end note

alt 受け入れアプリがインストール済み + パス一致
  iOS --> A: NSUserActivityTypeBrowsingWeb(url)
  note right of A #FFEECC
  C.要アプリ実装(URL受信と保留)
  end note

  A -> A: DeepLinkRouter.handle(url)
  note right of A #FFEECC
  D.要アプリ実装(URL解析とルーティング)
  end note

  A --> User: 対象画面を表示
  note right of A #FFEECC
  E.要アプリ実装(UI遷移の実行)
  end note
else 未インストール or パス不一致(またはSafari優先)
  iOS --> Safari: https://example.com/... を開く
  opt Web側でApp誘導(任意)
    Safari -> Store: Appページ表示(ユーザー操作)
  end
end

@enduml
@startuml
title Universal Links 関係図(起点修正+分岐理由)

left to right direction

actor "User" as USER
rectangle "Web Server" as WEB
node "Apple CDN" as CDN
node "iPhone" as PHONE
cloud "Webサイト" as SITE
component "iPhone(受け入れアプリ)" as APP

' 事前フロー(番号外)
note left of WEB
事前:AASA配置
end note

' 実行フロー(起点)
USER --> PHONE : ① リンクをタップ

' AASA参照(キャッシュ/必要時取得)
WEB --> CDN : ②
CDN --> PHONE :' 開き先(④の分岐理由を注記)
PHONE --> APP :note on link #FFEECC
条件:アプリインストール済み +
AASA一致(ドメイン/パス等)
→ 受け入れアプリ起動
end note

PHONE --> SITE :note on link #FFEECC
条件:未インストール/AASA不一致/
ユーザーがSafari優先を選択/
.open(universalLinksOnly)失敗時
→ Webサイトを開く
end note

@enduml

# 参考URL

  • https://tech.yappli.io/entry/universal-links
  • https://qiita.com/omo_taku/items/4fd439fb8c02e999cc94
  • https://sg.wantedly.com/companies/wantedly/post_articles/305303
  • https://zenn.dev/hsylife/articles/72edd8b6234576
  • https://qiita.com/m-komatsu/items/2ca7cfa6426802ea9cb5
  • https://zenn.dev/ryodeveloper/articles/kame_ga_15_hiki