앱에서 유니버설 링크를 지원하기 위해서는 apple-app-site-association(AASA) 파일을 작성해서 웹 서버에 올려두는 작업이 필요합니다.

Apple JSON Metadata file

iOS 12 이전

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "<TEAM_DEVELOPER_ID>.<BUNDLE_IDENTIFIER>",
        "paths": ["*"]
      },
      {
        "appID": "<TEAM_DEVELOPER_ID>.<BUNDLE_IDENTIFIER>",
        "paths": ["/articles/*"]
      },
      {
        "appID": "<TEAM_DEVELOPER_ID>.<ANOTHER_APP_BUNDLE_IDENTIFIER>",
        "paths": ["/blog/*", "/articles/*"]
      }
    ]
  }
}

주의 사항

  • apps는 빈 배열이어야 합니다.
  • 파일 이름은 json 확장자가 없는 apple-app-site-association이어야 합니다.
  • 파일 위치는 http://foobar.com/apple-app-site-association입니다.

iOS 13 이후

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
  "applinks": {
      "details": [
           {
             "appIDs": [ "ABCDE12345.com.example.app", "ABCDE12345.com.example.app2" ],
             "components": [
               {
                  "#": "no_universal_links",
                  "exclude": true,
                  "comment": "Matches any URL whose fragment equals no_universal_links and instructs the system not to open it as a universal link"
               },
               {
                  "/": "/buy/*",
                  "comment": "Matches any URL whose path starts with /buy/"
               },
               {
                  "/": "/help/website/*",
                  "exclude": true,
                  "comment": "Matches any URL whose path starts with /help/website/ and instructs the system not to open it as a universal link"
               }
               {
                  "/": "/help/*",
                  "?": { "articleNumber": "????" },
                  "comment": "Matches any URL whose path starts with /help/ and which has a query item with name 'articleNumber' and a value of exactly 4 characters"
               }
             ]
           }
       ]
   },
   "webcredentials": {
      "apps": [ "ABCDE12345.com.example.app" ]
   }
}

주의 사항

  • 파일 이름은 json 확장자가 없는 apple-app-site-association이어야 합니다.
  • 파일 위치는 http://foobar.com/.well-known/apple-app-site-association입니다.

Apple JSON Metadata file Signing

apple-app-site-association 파일은 Signing을 해서 저장해야 합니다. 여기서는 OpenSSL을 기준으로 기술합니다.

1
openssl smime -sign -nodetach -in "./apple-app-site-association.json" -out "apple-app-site-association" -outform DER -inkey hostname.domain.key -signer hostname.domain.crt

만약 key, crt 확장자 파일 대신 pem 확장자 파일만 있다면 OpenSSL을 통해 생성 할 수 있습니다.

1
2
openssl rsa -in key.pem -text > hostname.domain.key
openssl x509 -inform PEM -in cert.pem -out hostname.domain.crt

웹서버에 올리는 파일은 텍스트 json 파일이 아닌 signing 한 json 확장자 없는 파일을 올려야 합니다.

웹서버 설정

apple-app-site-association 파일에 대해 Content-type을 변경해야 합니다. 여기서는 Nginx를 기준으로 기술합니다.

1
2
3
4
5
6
7
location ~ /.well-known/apple-app-site-association {
    default_type application/json;
}

location ~ /apple-app-site-association {
    default_type application/json;
}

주의 사항

  • https를 통해 파일에 접근하도록 해야 합니다.
  • https 인증서는 apple-app-site-association 파일을 signing 한 것과 같은 인증서를 써야 합니다.
  • 유니버설 링크를 사용하는 사이트와 apple-app-site-association 파일은 서로 다른 도메인이어야 합니다.

App ID Configuration

  • Associated Domains Capabilities를 Enabled 해야 합니다.
  • Xcode의 프로젝트 설정의 Signing & Capabilitites 탭의 Associated Domains에 다음 항목들을 추가합니다.
1
2
3
4
applinks:foobar.com
applinks:ulink.foobar.com
applinks:www.foobar.com
webcredentials:ulink.foobar.com

위 예시는 도메인이 foobar.com이며, Apple JSON Metadata File 파일을 ulink.foobar.com에서 제공하는 경우입니다.

AppDelegate 구현

iOS 9.1 이후

1
2
3
4
5
6
7
8
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
    NSURL *url = userActivity.webpageURL;
    // ...
}

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
    // ...
}

iOS 13 이후

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let urlToOpen = userActivity.webpageURL else {
            return
    }

    // ...
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    for urlContext in URLContexts {
        let urlToOpen = urlContext.url
        // ...
    }
}

참고

  • Apple JSON Metadata File은 앱 설치 후 첫 실행 시 읽습니다. 그 이후로는 캐시를 활용하므로 개발 중 변경 시 Safari의 캐시를 삭제하고 앱 또한 삭제 후 재설치 하는 과정이 필요합니다.