Search code examples
iosreactjsfirebaseios-universal-linksapplinks

How to setup Associated Domains / Universal Links


I am trying to redirect the user to the installed app when visiting www.example.com/success as well as show a banner when visiting the homepage www.example.com.

With my current implantation the url DOES NOT redirect and open the app and neither does the website display a banner.

I am following the documentation found here https://developer.apple.com/documentation/safariservices/supporting_associated_domains

My website is a simple create-react-app with a homepage hosted with Firebase.

I have done the following:

  1. Add Associated Domains to Signing and Capabilities - applinks:website.com/success
  2. Add apple-app-site-association file to /public/
  3. Amend firebase.json file
  4. Link to files in index.html

What I am using to validate:

https://branch.io/resources/aasa-validator/

https://search.developer.apple.com/appsearch-validation-tool

Testing by adding www.example.com/success link to notes and opening, It always opens in safari. I Have also tried reinstalling the app and restarting the phone.

Apple API Validation

I have uploaded a new app version with associated domains to the store

enter image description here

Branch.io

{
    "applinks": {
        "apps": [],
        "details": []
    }
}

When visiting www.example.com/apple-app-site-association

{ "activitycontinuation": { "apps": [ "team.com.example.com" ] }, "applinks": { "apps": [], "details": [ { "appID": "team.com.example.com", "paths": [ "/", "/", "/success", "/success/",] }, "webcredentials": { "apps": [ "team.com.example.com" ] } }

apple-app-site-association

{
  "activitycontinuation": {
      "apps": [
        "team.com.example.com"
      ]
  },
  "applinks": {
      "apps": [],
      "details": [
            {
                "appID": "team.com.example.com",
                "paths": [
                  "/",
                  "/*", 
                  "/success",
                  "/success/*", 
                ]
            },
      ]
  },
  "webcredentials": {
      "apps": [
        "team.com.example.com"
      ]
  }
}

Index.html

<link rel="apple-app-site-association file" href="%PUBLIC_URL%/apple-app-site-association">
<link rel="apple-app-site-association file" href="/apple-app-site-association">
<meta name="App" content="app-id=XXX, app-argument=https://apps.apple.com/US/app/APP/idXXX, affiliate- data=optionalAffiliateData">

Firebase.json

{
  "hosting": {
    "public": "public",
    "headers": [
      {
        "source": "/apple-app-site-association",
        "headers": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ]
      }
    ],
    "appAssociation": "NONE"
  }
}

Device Console

When installing the app I monitor the device console in Xcode looking through swdc process logs for anything related to associated domains or requests. Here's some I found;

Error getting enterprise-managed associated domains data. If this device is not enterprise-managed, this is normal: Error Domain=SWCErrorDomain Code=1701 "Failed to get associated domain data from ManagedConfiguration framework." UserInfo={NSDebugDescription=Failed to get associated domain data from ManagedConfiguration framework., Line=298, Function=}

Entry { s = applinks, a = , d = au….ub….com, ua = unspecified, sa = approved } needs its JSON updated because the app PI changed

https://developer.apple.com/library/archive/qa/qa1916/_index.html


Solution

  • First at your device logs and your JSON format it seems your should be following the example shown here: https://developer.apple.com/documentation/safariservices/supporting_associated_domains

    In your logs it may shows logs from other apps who also have the outdated API format. However this should not be so much of an issue.

    {
      "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" ]
       },
        "appclips": {
            "apps": ["ABCED12345.com.example.MyApp.Clip"]
        }
    }
    

    Secondly following the steps in Apple's troubleshooting and your problem I am guessing your problem might be Step 7

    • Your app returned false from one of the following UIApplicationDelegate protocol methods: application(:continueUserActivity:restorationHandler:), application(:willFinishLaunchingWithOptions:), application(_:didFInishLaunchingWithOptions:).

      This can happen if you parsed the URL that is passed into these methods, and you implemented logic to determine that your app can not use this URL.

    You have not posted how you handle the URL. You need to implement the UIUserActivityRestoring method as suggested in the other answer for it to work. Here is a SwiftUI version

    ContentView()   
        .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
            
            print("Continue activity \(userActivity)")
            guard let url = userActivity.webpageURL else {
                return
            }
            
            print("User wants to open URL: \(url)")
            // TODO same handling as done in onOpenURL()
    
        }
    

    Also add

    .onOpenURL { url in
        print("URL OPENED")
        print(url) // parse the url to get someAction to determine what the app needs do
        if url.relativeString == "example://success" {
    
        }
    }
    

    https://developer.apple.com/library/archive/qa/qa1916/_index.html