AWS S3+CloudFront使ってUniversal Linksでアプリを起動する

Cognitoのユーザー認証を触っている流れで、認証後にアプリにユーザーを戻す処理でユニバーサルリンクを使用しました。

S3にapple-app-site-associationを配置する方法はこちらの記事（[Universal Linksを試してみました。関連づけファイル(apple-app-site-association）はS3に置きました。](https://dev.classmethod.jp/articles/universal-links/)）を参考にさせていただいていたのですが、実際にやってみると自分の環境では公開する静的サイトをhttpsに対応させることなども必要だったので行った手順をまとめました。

## ユニバーサルリンク用のS3とCloudFrontの設定

S3にユニバーサルリンク用のバケットを追加していきます。

![sc_01_s3_bucket.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/e82845c2-e374-7e5b-97d4-d47c9818060b.png)

次に適当なindex.htmlと.well-knownディレクトリの下にapple-app-site-associationファイルをアップしていきます。

階層的にはこんな感じになります。

S3バケット
┣━ .well-known ━ apple-app-site-association
┗━ index.html

apple-app-site-associationは以下のようなよくある感じのものをアップしました。
appIDに指定するTeam IDとバンドルIDはApple開発者サイトのIdentifiersのAppIDの詳細で確認できます。

```
{
  "applinks": {
       "apps": [],
        "details": [
           {
               "appID":"{Team ID}.{アプリのBundle ID}",
               "paths":[ "*" ]
           }
         ]
    }
}
```

ファイルをアップロード後にapple-app-site-associationのContent-Typeを`application/json`に変更しておきます。
apple-app-site-associationを選択してプロパティの項目からメタデータで変更できます。

![sc_02_s3_meta.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/c5fa6db1-8583-0532-448f-68e544801d34.png)


次に作成したバケットで **Static website hosting** を有効にします。

インデックスドキュメントにはindex.htmlを指定しておきます。

![sc_02a_s3_sw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/e3278baf-55fe-b73e-67fe-36f8c6818b86.png)


このままだとhttpのままなのでhttpsになるようにAWS CloudFrontを利用していきます。

![sc_03_cf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/b3bc304c-de7a-44ae-279e-14277d5cd3b6.png)

**CreateDistribution**を選択

![sc_04_cf_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/e030c7e7-eea9-8260-f1af-bf56920cd77c.png)


**Webの方のGet Started**を選択

![sc_05_cf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/e85cf1d5-cb88-b3b2-17d8-0b0556e3ffc1.png)

ここから詳細情報の入力になります。
今回設定した項目だけ箇条書きで出します。

* **Origin Domain Name**は選択候補が出てくるのでAmazon S3 Bucketの項目から先ほど追加したユニバーサルリンク用のバケットを選択する
* **Origin ID**はOrigin Domain Name選択後に自動入力されるのでそのまま
* **Restrict Bucket Access**はyesを選択
* **Grant Read Permissions on Bucket**はyesを選択
* **Viewer Protocol Policy**はRedirect HTTP to HTTPSを選択
* **Distribution Settings**
    * **Default Root Object** は今回はindex.html
* 一通り入力したら**Create Distribution**

作成に成功したら `****.cloudfront.net` というドメインが取得できますのでブラウザなどで https://****.cloudfront.net にアクセスしてindex.htmlの内容が表示されるのを確認します。

あとはhttps://****.cloudfront.net/.well-known/apple-app-site-association にもアクセスしてjsonが表示されていれば大丈夫です。


## ユニバーサルリンク用のアプリ設定 

アップルの開発者サイトでapple-app-site-association指定したAppIDのAssociated Domainsにチェックします。

![sc_06_apple_appid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/f6c2989e-8175-0c9b-b602-6c036fabd339.png)


アプリプロジェクトでSigning & Capabilitiesの項目でCapabilitiesを追加からAssociated Domainsを追加します。
そこのドメインに `applinks:****.cloudfront.net` となるようにします。

![sc_07_appuni.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64426/9b8ed5af-083c-af01-63cb-bd223abb925d.png)


これでアプリを端末インストール後に https://****.cloudfront.net のURLを読み込めばアプリが呼び出されます。

僕はSlackの個人チャットにURL貼って起動することを確認しました。

あとは起動時にAppDelegateの関数が呼ばれるのでそこで起動後の処理を行いますが、
もし、iOS13でSceneDelegateを使用している場合はこちらの処理は呼ばれないので注意してください。

```.swift
func application(_ application: UIApplication,
           continue userActivity: NSUserActivity,
           restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
      let incomingURL = userActivity.webpageURL,
      let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
      return false
    }
    // パラメータの解析とかして処理振り分ける
//    guard let path = components.path,
//      let params = components.queryItems else {
//        print("parameter missing.")
//        return false
//    }
    return true
}
```

iOS13以降でSceneDelegate使っている場合は**scene(_:willConnectTo:options:)** でURLが受け取れるのでここでパラメータチェックしていきます。

```.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    if let userActivity
        = connectionOptions.userActivities
            .first(where: { $0.webpageURL != nil }),
        let incomingURL = userActivity.webpageURL,
        let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) {
        // パラメータの解析など
        print("parameter: \(components)")
    }
    
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: LandingView())
        self.window = window
        window.makeKeyAndVisible()
    }
}
```


以上です。




#### 参考

* [Universal Linksを試してみました。関連づけファイル(apple-app-site-association）はS3に置きました。](https://dev.classmethod.jp/articles/universal-links/)
* [AWS S3でのhttps対応含む静的ウェブサイト公開](https://qiita.com/THacker/items/11eadffe6b3ce3491e3b)